美文网首页
[Common] Head First 设计模式 (策略 + 观

[Common] Head First 设计模式 (策略 + 观

作者: 木小易Ying | 来源:发表于2020-03-29 15:03 被阅读0次

    Chapter 1 设计模式入门

    Joe上班的公司做了一套相当成功的模拟鸭子游戏:SimUDuck。游戏中出现各种鸭子,一边游泳戏水(swim),一边呱呱叫(quack),注意是呱呱叫,而不是吱吱叫。此系统的内部设计使用了标准的OO技术,设计了一个鸭子超类(Superclass),并让各种鸭子继承此超类。

    继承但有些不是公用特点

    但,如果以后又加入新的鸭子类型,比如诱饵鸭(DecoyDuck),即不会飞也不会叫……还有很多,我们可以自己想得到。那么joe的噩梦来了,这种设计方式有一下几种缺点:

    1. 代码在多个子类中重复;
    2. 运行时的行为不容易改变;
    3. 很难知道所有鸭子的全部行为;
    4. 改变会牵一发而动全身,造成其他鸭子不想要的改变;

    Joe认识到继承可能不是答案,Joe知道规格会常常改变,每当有新的鸭子子类出现,他就要被迫检查并尽可能覆盖fly()方法和quark()方法……这简直是无穷的噩梦。

    用接口替代继承

    Joe的主管告诉他,这真是一个超笨的主意,这么一来重复的代码会变多,如果认为覆盖几个方法就算是差劲,那么对于48的Duck的子类都要稍微修改一下飞行的行为,又怎么说?

    不管你在何处工作,构建些什么,用何种编程语言,在软件开发上,一致伴随你的哪个不变的真理就是需求变更!幸运的是,有一个设计原则,恰好适用于此状况。

    设计原则:找出应用中可能需要变化之处,把他们独立出来,不要和哪些不需要变化的代码混在一起。

    换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。也就是说,把会变化的部分取出并封装起来,以便以后可以轻易地改动或拓展此部分,而不影响不需要变化的其他部分。

    设计原则:针对接口编程,而不是针对实现编程。

    将fly行为改为接口编程

    "针对接口编程" 真正的意思是 "针对超类型编程":这里的接口有多个含义,接口是一个概念,也是一种java的interface构造。"针对接口编程"关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际情况执行到真正的行为,不会被绑死在超类型的行为上。

    这句话可以更明确的说成变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口。如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着声明类时不用理会以后执行的真正对象类型。

    将变量声明为超类类型

    根据面向接口编程我们将鸭子的行为改成酱紫:


    鸭子的行为

    但Duck是不是也应该设计为接口类?在本例中这么做是木有必要的,因为已经将变化的部分抽离出来了,Duck已经是共有的属性和方法了,所以不用。

    public abstract class Duck {
        FlyBehavior flyBehavior;
        QuackBehavior quackBehavior;
    
        public Duck() {
        }
    
        abstract void display();
    
        public void performFly() {
            flyBehavior.fly();
        }
    
        public void performQuack() {
            quackBehavior.quack();
        }
    
        public void swim() {
            System.out.println("All ducks float, even decoys!");
        }
    }
    
    =====================
    
    package headfirst.designpatterns.strategy;
    
    public class MallardDuck extends Duck {
    
        public MallardDuck() {
            quackBehavior = new Quack();
            flyBehavior = new FlyWithWings();
        }
    
        public void display() {
            System.out.println("I'm a real Mallard duck");
        }
    }
    

    但这里MallardDuck的初始化仍旧依赖了实现,因为quackBehavior是接口类型,所以我们可以在运行时随意的指定不同的实现类

    public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }
    
    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
    

    这样就可以改变鸭子的行为啦:

    Duck model = new ModelDuck();
    model.performFly();    
    model.setFlyBehavior(new FlyRocketPowered());
    model.performFly();
    
    整体

    设计原则:多用组合,少用继承。


    ※ 策略模式

    定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。


    回顾:OO原则(封装、继承、多态、抽象)

    可参考:https://www.cnblogs.com/xuwendong/p/10607308.html & https://www.cnblogs.com/joyous-day/p/6226802.html

    • 封装--封装主要是基于类的属性,本类中的属性私有化,即有private修饰词来加以修饰,生成GET,SET方法来给外界调用。

    • 继承--java继承是面向对象的最显著的一个特征。继承是从已有的来中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

    • 多态--java的多态是指对非静态方法的多态,父类的引用来调用子类的方法。
      Override和Overload都是多态的表现:Override是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变;Overload是在一个类里面,方法名字相同,而参数不同

    • 抽象--使用关键词abstrace声明的类叫做“抽象类”。如果一个类里包含了一个或多个抽象方法,这个类就指定为抽象类。“抽象方法”属于一种不完整的方法,只含有一个声明,没有方法体。


    Chapter 2 观察者模式

    恭喜贵公司获选为敝公司建立下一代Internet气象观测站!该气象站必须建立在我们专利申请中的WeatherData对象上,由WeatherData对象负责追踪目前的天气状况(温度、湿度、气压)。我们希望贵公司能建立一个应用,有三种布告板,分别显示目前的状况、气相统计以及简单的预报。当WeatherData对象获得最新的测量数据时,是那种布告板必须实时更新。

    错误示范

    这样做的问题是针对实现编程而非接口、对于每一个新布告板都得修改代码、不能运行时动态增删布告板、也没有封装变化的部分。


    ※ 观察者模式

    定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新


    实现方式有很多种,其中一种比较常见的是酱紫的:


    观察者实现

    当2个对象之间松耦合,他们依然可以交互,但是不太清楚彼此的细节。观察者提供了一种对象设计,让subject和observer之间松耦合。

    关于观察者的一切,主题只知道观察者实现了某个接口(也就是observer接口),主题不需要知道观察者的具体类是谁,做了些什么等其他细节。

    任何时候我们都可以增加观察者,因为主题唯一依赖的东西是一个实现observer接口的对象列表,所以我们可以随时增加观察者。

    设计原则:为了交互对象之间的松耦合设计而努力。

    根据观察者模式对气象站的实现做了修改:


    通过观察者修改气象站

    有一个地方比较好玩,就是布告板的init传入了weather data用于增加观察者:

    public  CurrentConditionsDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    //只是把最近的温度展示出来
    @Override
    public void show() {
        System.out.println("Current conditions: "+tempreture+",F degrees and"+
        humidity+"% humidity");
    }
    
    @Override
    public void update(float temp, int humidity, float pressure) {
        //我们把温度和湿度保存起来,然后调用展示数据的方法
        this.tempreture = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        show();
    }
    

    这也是解耦的一个方面,如果做单元测试的时候不想监听weatherdata了的话,为了不去改布告板的内部代码,把被监听的subject通过传入构造器的方式更灵活。而且如果想要取消观察会更方便。


    JDK中的observer

    Observable 是一个类,而不是一个接口,这点是与上面subject不同的。Observable类追踪所有的观察者,并通知他们。

    首先,你需要利用扩展Java.util.Observerable 接口 产生可观察者类,然后,需要先调用setChanged()方法,标记状态已经改变的事实;然后调用两种notifyObservers()方法中的一个:notifyObservers()notifyObservers(Object arg)

    观察者同以前一样,实现了更新的方法,但是方法的签名不太一样:update( Observers o ,Object arg); //当通知时,此版本可以传送任何的数据对象给每一个观察者

    如果你想推送数据给观察者,你可把数据当做数据对象传送给notifyObservers(Object arg) 方法。否则,观察者就必须从可观察者中拉数据。

    通过JDK的observable改造气象站

    其中的setChanged()方法用来标记状态已经改变的事实,好让notifyObservers()知道当它被调 用时应该更新观察者。如果调用notifyObservers()之前没有先调用setChanged(),观察者就“不会”被通知。从Observable源码分析了解下:

        private boolean changed = false;
        //setChanged()方法把changed标志设 为true。
        protected synchronized void setChanged() {
            changed = true;
        }
        protected synchronized void clearChanged() {
            changed = false;
        }
        //notifyObservers() 只会在 changed标为“true”时通知观 察者。
        public void notifyObservers(Object arg) {
            Object[] arrLocal;
            synchronized (this) {
                if (!changed)
                    return;
                arrLocal = obs.toArray();
                clearChanged();
            }
            for (int i = arrLocal.length-1; i>=0; I--)
                ((Observer)arrLocal[i]).update(this, arg);
        }
    

    通过setChanged()方法可以让你在更新观察者时,更适当地通知观察者,这样会使程序有更多的弹性。比如,如果没有setChanged()方法,气象站测量太过精确, 以致于温度计读数每十分之一度就会更新,这会造成WeatherData对象持续不断地通知观察者,浪费了资源。如果我们希望半度以上才更新,就可以在温度差距到达半度时,调用setChanged()(加个if语句判断即可)进行有效的更新。 或许我们不会经常用到此功能,但是把这样的功能准备好,需要时就可以马上使用。如果此功能在某些地方对你有帮助,你可能也需要clearChanged()方法,将changed状态设置回false。另外也有一个hasChanged()方法, 告诉你changed标志的当前状态。

    我们来看下如何用pull的方式获取数据,而不让subject将自己的状态push出去:

    import java.util.Observable;
    
    public class WeatherData extends Observable {
        //湿度
        private float temperature;
        //温度
        private float humidity;
        //气压
        private float pressure;
    
        public WeatherData() {
        }
    
        //测量结果改变时,只有先将changed状态改为true时,通知所有观察者方法才会被调用
        public void measurementsChanged(){
            setChanged();
            notifyObservers();
        }
    
        //模拟手动设置测量结果(湿度,温度,气压)
        public void setMeasurements(float temperature,float humidity,float pressure){
            this.temperature = temperature;
            this.humidity = humidity;
            this.pressure = pressure;
            measurementsChanged();
        }
    
        public float getTemperature() {
            return temperature;
        }
    
        public float getHumidity() {
            return humidity;
        }
    
        public float getPressure() {
            return pressure;
        }
    }
    
    ====================
    
    public class CurrentConditionsDisplay implements Observer, DisplayElement {
        //湿度
        private float temperature;
        //温度
        private float humidity;
        //气压
        private float pressure;
        //
        private Observable observable;
    
        //运用多态传入其Observable子类对象即可
        public CurrentConditionsDisplay(Observable observable) {
            this.observable = observable;
            observable.addObserver(this);
        }
        
        //现在自己去主题对象中拿数据
        @Override
        public void update(Observable o, Object arg) {
            if (o instanceof WeatherData){
                WeatherData weatherData = (WeatherData) o;
                this.temperature = weatherData.getTemperature();
                this.humidity = weatherData.getHumidity();
                this.pressure = weatherData.getPressure();
                display();
            }
        }
    
        @Override
        public void display() {
            System.out.println("目前状况:湿度为" + temperature + "RH; 温度为:" + humidity + "度;气压为" + pressure + "帕斯卡");
        }
    }
    

    当然Java自带的观察者模式类和接口也有缺点,比如主题Observable是一个“类”而不是一个接口,我们必须设计一个子类来实现它,如果该子类还要继承其他的父类,就无法做到了,因为Java不支持多继承,这限制了Observable的复用能力。而且setChanged方法被用protected关键字保护起来了,只有你继承了Observable类才能使用,无法将其组合到自己的对象中,违反了设计原则:多用组合,少用继承。

    有木有发现这个比我们最开始依次调用update(temp, humidity, pressure)要好很多?如果将来增加了属性也不用改每个观察者的方法,只要改subject就可以了,这也是封装变化的一个方面。

    • 观察者模式允许push(推)或拉(pull)的方式传送数据。(最好是push)
      有多个观察者时,不可以依赖特定的通知次序。

    Chapter 3 装饰者模式

    欢迎来到星巴兹咖啡,该公司是世界上以扩张速度最快而闻名的咖啡连锁店。但是最近这家著名的咖啡公司遇到一个巨大的问题,因为扩展速度太快了,他们准备更新订单系统,以合乎他们的饮料供应需求。

    星巴兹咖啡

    然后客户购买咖啡时,可以要求在其中加入任何调料,例如:奶茶,牛奶,豆浆。星巴兹根据业务需求会计算相应的费用。这就要求订单系统必须考虑到这些调料的部分。

    如果每加一种配料都算做一个新的class,就会有一种犯了密集恐惧症的感觉,完全就是“类爆炸”。

    这样肯定是不可以的,那我们可以考虑把配料作为实例变量,cost会根据实例变量来计算价格。

    通过实例变量改造

    到考虑设计模式就得想到之后的变化。

    看起来很完美,也能满足现有的业务需求,但是仔细思考一下,真的这样设计不会出错?回答肯定是会出错。

    • 一旦调料的价格发生变化,会导致我们队原有代码进行大改。
    • 一旦出现新的调料,我们就需要加上新的方法,并需要改变超类Beverage类中cost()方法。
    • 如果星巴兹咖啡研发新的饮料。对于这些饮料而言,某些调料可能并不合适,但是子类仍然会继承那些本就不合适的方法,例如我就想要一杯水,加奶泡(hasWhip)就不合适。
    • 如果用户需要双倍的摩卡咖啡,又应该怎么办呢?

    设计原则:类应该对拓展开放,对修改关闭。

    那么什么是开放,什么又是关闭?开放就是允许你使用任何行为来拓展类,如果需求更改(这是无法避免的),就可以进行拓展!关闭在于我们花费很多时间完成开发,并且已经测试发布,针对后续更改,我们必须关闭原有代码防止被修改,避免造成已经测试发布的源码产生新的bug。

    综合上述说法,我们的目标在于允许类拓展,并且在不修改原有代码的情况下,就可以搭配新的行为。如果能实现这样的目标,带来的好处将相当可观。在于代码会具备弹性来应对需求改变,可以接受增加新的功能用来实现改变的需求。没错,这就是拓展开放,修改关闭。

    那么有没有可以参照的实例可以分析呢?有,就在第二篇我们介绍观察者模式时,我们介绍到可以通过增加新的观察者用来拓展主题,并且无需向原主题进行修改。

    我们是否需要每个模块都设计成开放--关闭原则?不用,也很难办到(这样的人我们称为“不用设计模式会死病”)。因为想要完全符合开放-关闭原则,会引入大量的抽象层,增加原有代码的复杂度。我们应该区分设计中可能改变的部分和不改变的部分(第一设计原则),针对改变部分使用开放--关闭原则。


    现在来用装饰者模式来改造一下星巴兹咖啡的计算:

    这就需要一个新的设计思路。这里,我们将以饮料为主,然后运行的时候以饮料来“装饰”饮料。举个栗子,如果影虎需要摩卡和奶泡深焙咖啡,那么要做的是:拿一个深焙咖啡(DarkRosat)对象、以摩卡(Mocha)对象装饰它、以奶泡(Whip)对象装饰它、调用cost方法,并依赖委托将调料的价钱加上去。

    装饰者
    • 装饰者和被装饰对象有相同的超类型。
    • 你可以用一个或多个装饰者包装一个对象。
    • 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
    • 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
    • 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象

    什么是装饰模式呢?我们首先来看看装饰模式的定义:

    装饰者模式动态地将责任附加到对象上。 若要扩展功能,装饰者提供了比继承更有弹性的替代方案。


    下面我们用装饰者模式改造一下星巴兹咖啡:

    通过装饰者修改

    从类图我们看到,CondimentDecorator扩展自Beverage类,用到了继承。这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的父类,这是相当关键的地方。这里的继承是达到“类型匹配”,而不是利用继承获得“行为”。

    那么行为又是从哪里来的呢?
    当我们将装饰者和组件组合时,就是在加入新的行为。所得到的新行为,并不是继承自父类,而是由组合对象得来的。也就是说,继承Beverage类,是为了有正确的类型,而不是继承它的行为。行为来自装饰者和基础组件,或与其他装饰者之间的组合关系。

    因为使用对象组合,就可以把所有奶茶和配料更有弹性地加以混合和匹配,非常方便。如果依赖继承,那么类的行为只能在编译时静态决定,行为不是来自父类,就是子类覆盖后的版本。反之,利用组合,可以把装饰者混合使用,而且是在“运行时”!!!

    为什么不把Beverage设计成一个接口,而是抽象类呢?
    通常装饰者模式是采用抽象类,但是在Java中可以使用接口。尽管如此,我们都努力避免修改现有的代码,所以,如果抽象类运作的好好地,还是别去修改它。

    public abstract class Beverage {
        String description="Unknown Beverage";
        
        public String getDescription(){
            return description;
        }
        
        public abstract double cost();
    }
    
    
    public abstract class CondimentDecorator extends Beverage {
        //所有的调料装饰者都必须重新实现 getDescription()方法。 
        public abstract String getDescription();
    }
    
    
    public class Espresso extends Beverage {
        
        public Espresso(){
            //为了要设置饮料的描述,我 们写了一个构造器。记住, description实例变量继承自Beverage1
            description="Espresso";
        }
        
        public double cost() {
            //最后,需要计算Espresso的价钱,现在不需要管调料的价钱,直接把Espresso的价格$1.99返回即可。
            return 1.99;
        }
    }
    

    下面让我们看一下调料的代码:

    public class Mocha extends CondimentDecorator {
        /**
         * 要让Mocha能够引用一个Beverage,采用以下做法
         * 1.用一个实例记录饮料,也就是被装饰者
         * 2.想办法让被装饰者(饮料)被记录在实例变量中。这里的做法是:
         * 把饮料当作构造器的参数,再由构造器将此饮料记录在实例变量中
         */
        Beverage beverage;
        
        public Mocha(Beverage beverage) {
            this.beverage=beverage;
        }
        
        public String getDescription() {
            //这里将调料也体现在相关参数中
            return beverage.getDescription()+",Mocha";
        }
        
        /**
         * 想要计算带摩卡的饮料的价格,需要调用委托给被装饰者,以计算价格,
         * 然后加上Mocha的价格,得到最终的结果。
         */
        public double cost() {
            return 0.21+beverage.cost();
        }
    }
    

    然后来看下测试的代码:

    public class StarbuzzCoffe {
        public static void main(String[] args) {
            //订购一杯Espresso,不需要调料,打印他的价格和描述
            Beverage1 beverage=new Espresso();
            System.out.println(beverage.getDescription()+"$"
                    +beverage.cost());
            
            //开始装饰双倍摩卡+奶泡咖啡
            Beverage1 beverage2=new DarkRoast1();
            beverage2=new Mocha(beverage2);
            beverage2=new Mocha(beverage2);
            beverage2=new Whip(beverage2);
            
            System.out.println(beverage2.getDescription()+"$"
                    +beverage2.cost());
            
            //
            Beverage1 beverage3=new HouseBlend();
            beverage3=new Soy(beverage3);
            beverage3=new Mocha(beverage3);
            beverage3=new Whip(beverage3);
            
            System.out.println(beverage3.getDescription()+"$"
                    +beverage3.cost());
        }
    }
    

    装饰者的确会有很多对象,但是它主要是给工厂类服务的,所以会把内部封装的很好防止使用者出错。


    JAVA的I/O也是典型的装饰者模式

    Java I/O

    硬币有正反两面,装饰者模式也有一个“缺点”:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用相关API(如java.io)程序员的困扰,不过知道了装饰者的工作原理,以后就能很容易地辨别出它们的装饰者类是如何组织的,以方便用包装方式取得想要的行为。


    Chapter 4 烘烤OO的精华

    如果有一个披萨店,需要根据用户点的披萨进行加工,我们可以创建一个Pizza抽象类,然后根据用户的点单进行动态实例化:

    披萨店

    但这里的new是很依赖具体实现的,如果我们怎加了一个披萨类型,或者减少了都要改这里的orderPizza。

    那要怎么封装变化呢?也就是把创建披萨的代码封装起来,让一个对象专门负责创建pizza,这就是创建披萨的工厂:

    // 只做一件事情,帮它的客户创建比萨。
    public class SimplePizzaFactory {
    
        // 首先,在这个工厂内定义一个createPizza()方法。所有客户用这个方法来实例化新对象。
        public Pizza createPizza(String type) {
            Pizza pizza = null;
    
            if (type.equals("cheese")) {
                pizza = new CheesePizza();
            } else if (type.equals("pepperoni")) {
                pizza = new PepperoniPizza();
            } else if (type.equals("clam")) {
                pizza = new ClamPizza();
            } else if (type.equals("veggie")) {
                pizza = new VeggiePizza();
            }
            return pizza;
        }
    }
    

    这样做的话就可以让这个工厂给很多其他的东西提供对象了,不仅仅是这家披萨店,如果需要修改也只要改工厂就好。很多工厂的方法都是静态的,这样就不用创建factory对象,但是不太好的是这个create的方法就写死了不能通过继承之类的覆写。

    用工厂改写披萨店是酱紫的:

    public class PizzaStore{
      SimplePizzaFactory factory;
      public PizzaStore(SimplePizzaFactory factory) {
        this.factory=factory;
      }
    
      public Pizza orderPizza(string type) {
        Pizza=pizza;
        pizza=factory.CreatePizza(type);
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
      }
    }
    
    简单工厂

    但是如果由于经营得当,开分店已经是这个世界上最正常不过的事情了,为了体现地方特色,我们希望我们开的分店(或是说加盟店)能够加入当地的特色来做披萨,所以,我们把披萨的生产下放到每一个分店中。下面是我们重新设计的代码:

    public abstract class PizzaStore
        {
            public Pizza OrderPizza(string pizzaType)
            {
                Pizza pizza = new Pizza();
                pizza = CreatePizza(pizzaType);
                pizza.PrePare();
                pizza.Bake();
                pizza.Cut();
                pizza.Box();
                return pizza;
            }
            public abstract Pizza CreatePizza(string pizzaType);
        }
    
    
    public class NYPizzaStore : PizzaStore
        {
            public Pizza CreatePizza(string item)
            {
                if (item.Equals("cheese"))
                    return new NYCheesePizza();
                else
                    return null;
            }
        }
    

    工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类时哪一个。工厂方法让类把实例化推迟到子类,来达到将对象创建的过程封装的目的。

    在工厂模式里面,我们要弄清楚两个重要的角色。

    1. 创建类(Creator)类
      这是抽象创建者类,它定义了一个抽象的工厂方法,让子类实现此方法制造产品。创建者通常会包含依赖于抽象产品的代码,而这些抽象产品偶子类制造。创建者不需要真的知道在制造哪中具体产品。也就是这里的PizzaStore类。其中的CreatePizza()方法正是工厂方法,用来制造产品。

    2. 产品类,也就是这里的Pizza类。工厂生产产品。对PizzaStore来说,产品就是Pizza。

    这两个类层级为什么是平行的:因为它们都有抽象类,而抽象类都有许多具体的子类,每个之类都有自己特定的实现。

    简单工厂和工厂方法的区别:
    子类的确看起来很像简单工厂。简单工程把全部的事情,在一个地方都处理完了,然而工厂方法却是创建了一个框架,让子类决定要如何实现。

    比方说,在工厂方法中,orderPizza()方法提供了一个一般的框架,以便创建披萨,orderPizza()方法依赖工厂方法创建具体类,并制造出实际的披萨。可通过继承PizzaStore()类,决定实际制造出的披萨是什么。简单工厂的做法,可以将对象创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工程不能变更正在创建的产品。


    依赖倒置原则 (Dependency Inversion Principle)

    要依赖抽象,不要依赖具体类。不要让高层组件依赖于底层组件,而且,不管高层或低层组件,“两者”都应该依赖于抽象。所谓“高层”组件,是由其他底层组件定义其行为的类。

    例如,PizzaStore是个高层组件,因为它的行为是由披萨定义的:PizzaSotre创建所有不同的比萨对象,准备。烘烤。切片,装盒。而比萨本身属于底层组件。

    1. 变量不可以持有具体类的引用。如果使用new,就会持有具体类的引用。你可以改用工厂来避开这样的做法。

    2. 不要让类派生自具体类。如果派生自具体类,你就会依赖具体类。请派生自一个抽象(接口或抽象类)。

    3. 不要覆盖基类中已经实现的方法。如果覆盖基类已经实现的方法,那么你的基类就不是一个真正适合被继承的抽象基类中已经实现的方法,应该有所有的子类共享。

    应用工厂类以后高层和底层组件都依赖pizza抽象类

    如果说建一个披萨店,第一想到的是有很多披萨,从生产到包装的流水线,这样就是从顶端开始想;让我们反过来,从披萨开始想能抽象什么,这就是倒置。

    --

    如果各种披萨用不同的风味的配料,也可以用不同的配料工厂来实现,例如:


    调味工厂 纽约风味披萨店

    在原料工厂的部分,引入了抽象工厂,也就是会有一个factory抽象类,拥有create各种的方法,而各种特殊风味调料的创建工厂实现这个抽象工厂。

    抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

    抽象工厂创造配料

    抽象工厂比较神奇的是,他的每个抽象方法的具体实现都是一个工厂的概念,例如createXXX。抽象工厂会有很多这种create的方法,也就是会组织一群相关的产品的创建,而不止一种。

    所以以上一共有三种工厂:

    1. 简单工厂: 由一个类来创建对象
    2. 工厂方法:父类规定创建对象的create方法,子类继承并实现各自的create方法
    3. 抽象工厂:抽象类定义一群create方法,再创建几个工厂实现抽象工厂,然后传入工厂实例给其他用到的对象,用组合的方式提供产品给需要的人

    相关文章

      网友评论

          本文标题:[Common] Head First 设计模式 (策略 + 观

          本文链接:https://www.haomeiwen.com/subject/vpcquhtx.html