美文网首页Android开发Android开发经验谈Android技术知识
那些看似不同其实一样的设计模式,让我用有一句话总结它们

那些看似不同其实一样的设计模式,让我用有一句话总结它们

作者: 唐子玄 | 来源:发表于2019-05-12 10:55 被阅读26次

虽然不同的设计模式解决的问题各不相同,但从一个更高的抽象层次来看,它们通过相同的手段来实现相同的目的,用一句话总结:它们增加了一层抽象变化封装起来,然后对抽象编程,并利用多态应对变化

本文将以更抽象的视角剖析工厂模式、策略模式、模版方法模式,以及这些模式所遵循的设计原则。

工厂模式

1. 变化是什么

对工厂模式来说,变化就是构建对象的方式,举个例子:

public class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza ;
    
        //构建具体pizza对象
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }else if(type.equals("bacon")){
            pizza = new BaconPizza();
        }
    
        //使用pizza对象
        pizza.prepare();
        pizza.bake();
        return pizza ;
    }
}

抽象的反义词是具体,对于工厂模式,具体就是用new来构建对象。这样做的后果是PizzaStore不仅需要引入具体的Pizza类,而且和构建Pizza的细节耦合在一起。

如果PizzaStore一辈子只做这两种Pizza,上面的代码就很好,不需要重构。但如果需要新增Pizza类型,就不得不修改orderPizza(),向其中增加if-else

2. 如何应对变化

通过将变化封装在一层新的抽象中,实现了上层代码和变化的隔离,达到解耦的目的。对于工厂模式来说,新建的抽象叫做工厂,它将对象的构建和对象的使用分隔开,让使用对象的代码不依赖于构建对象的细节。

解耦的好处是:“当变化发生时上层代码不需要改动”,这句话也可以表达成:“在不修改既有代码的情况下扩展功能”。这就是著名的 “开闭原则”

  • “对修改关闭”的意思是:当需要为类扩展功能时,不要想着去修改类的既有代码,这是不允许的! 为啥不允许?因为既有代码是由数位程序员的努力,历经了多个版本的迭代,好不容易才得到的正确代码。其中蕴含着博大精深的知识,和你不曾了解的细节,修改它一定会出bug的!
  • “对扩展开放”的意思是:类的代码应该具备良好的抽象,使得扩展类的时候,不需要修改类的既有代码。

现实的问题来了,如果项目中的既有类不具备扩展性,甚至是牵一发动全身的那种类。在一个时间较紧的迭代中需要往里添加新功能,你会怎么做?是违背“对修改关闭”,还是咬牙重构?(欢迎讨论~~)

3. 三种封装变化方式

  1. 简单工厂模式

既然目的是消除orderPizza()中构建具体pizza的细节,那最直接的做法是,将他们提取出来放到另一个类中:

public class PizzaFactory{
    public static Pizza createPizza(String type){
        Pizza pizza ;
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }else if(type.equals("bacon")){
            pizza = new BaconPizza();
        }
        return pizza ;
    }
}

然后使用Pizza的代码就变成:

public class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza = PizzaFactory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        return pizza ;
    }
}

等等,这和我们平时将一段常用代码抽离出来放到Util类中有什么区别吗?

是的,没有任何区别。从严格意义上说,这不是一个设计模式,更像是一种编程习惯。虽然只是代码搬家,但这种习惯的好处是:它隐藏了构建对象的细节,因为构建对象是经常会发生变化的,所以它还封装了变化,最后它还可以被复用,比如菜单类也需要构建 pizza 对象并获取他们的价格。

使用静态方法是这类封装常用的技巧,它的好处是不需要新建工厂对象就可以实现调用,但缺点是不具备扩展性(静态方法不能被重写)。

  1. 工厂方法模式

简单工厂模式中,工厂能够构建几种对象是在编译之前就定义好的,如果想要新增另一种新对象,必须修改既有的工厂类。这不符合开闭原则。 所以简单工厂模式对于新增对象类型这个场景来说显得不够有弹性。

有没有办法不修改既有类就新增对象类型?

工厂方法模式就可以做到,因为它采用了继承:

//抽象pizza店
public abstract class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        return pizza ;
    }
    //不同地区的pizza店可以推出地方特色的pizza
    protected abstract Pizza createPizza(String type) ;
}

//A商店提供芝士和培根两种pizza
public class PizzaStoreA extends PizzaStore{
    @Override
    protected Pizza createPizza(String type){
        Pizza pizza ;
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }else if(type.equals("bacon")){
            pizza = new BaconPizza();
        }
        return pizza ;
    }
}

简单工厂模式将构建对象的细节封装在一个静态方法中(静态方法无法被继承),而工厂方法模式将其封装在一个抽象方法中,这样子类可以通过重写抽象方法新增 pizza。

现在是介绍另一个设计原则的绝佳时机,它就是 “依赖倒置原则” :上层组件不能依赖下层组件,并且它们都不能依赖具体,而应该依赖抽象。

上面的例子中PizzaStore是上层组件,CheesePizza是下层组件,如果直接在PizzaStore中构建CheesePizza就违反了依赖倒置原则,经过工厂模式的重构,PizzaStore依赖于Pizza这个抽象,同时CheesePizza也依赖于这个抽象。所以违反依赖倒置会让代码缺乏弹性,不易扩展。

Android 中RecyclerView.Adapter就运用了工厂方法模式:

public abstract static class Adapter<VH extends ViewHolder> {
    //封装了各式各样ViewHolder的构建细节,延迟实现构建细节到子类中
    public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
}
  1. 抽象工厂模式

如果需要构建一组对象怎么办?

抽象工厂模式用来处理这种情况,它将构建一组对象的细节封装在一个接口中:

//抽象原料工厂(原材料构建者)
public interface IngredientFactory{
    void Flour createFlour() ;
    void Sause createSause();
}

//原材料使用者
public class Pizza{
    private Flour flour;
    private Sause sause;
    //使用组合持有构建者
    private IngredientFactory factory;
    
    //注入一个具体构建者
    //同一种pizza,在不同地区可能会有不同口味
    //那是因为虽然用的是同类原材料(抽象),当产地不同味道就不同(具体)
    public Pizza(IngredientFactory factory){
        this.factory = factory;
    }
    
    //使用具体工厂构建原材料(发生多态的地方)
    public void prepare(){
        flour = factory.createFlour();
        sause = factory.createSause();
    }
    
    public void bake(){}
    public void cut(){}
}

//具体工厂
public class FactoryA implements IngredientFactory{
    public Flour createFlour(){
        return new FlourA();
    }
    
    public Sause createSause(){
        return new SauseA();
    }
}

//构建pizza的时候传入具体工厂
public class PizzaStoreA extends PizzaStore{
    @Override
    protected Pizza createPizza(String type){
        Pizza pizza ;
        FactoryA factory = new FactoryA();
        if(type.equals("cheese")){
            pizza = new CheesePizza(factory);
        }else if(type.equals("bacon")){
            pizza = new BaconPizza(factory);
        }
        return pizza ;
    }
}

如果地区B开了一家新pizza店,只需要新建FactoryB并在其中定义地区B原材料的构建方式,然后传入Pizza类,整个过程不需要修改Pizza基类。

抽象工厂模式 和 工厂方法模式 的区别在于:

  1. 前者适用于构建多个对象,并且使用组合。
  2. 后者适用于构建单个对象,并且使用继承。

策略模式

1. 变化是什么

对策略模式来说,变化就是一组行为,举个例子:

public class Robot{
    public void onStart(){
        goWorkAt9Am();
    }
    public void onStop(){
        goHomeAt9Pm();
    }
}

机器人每天早上9点工作。晚上9点回家。公司推出了两款新产品,一款早上8点开始工作,9点回家。另一款早上9点工作,10点回家。

面对这样的行为变化,继承是可以解决问题的,不过你需要新建两个Robot的子类,重载一个子类的onStart(),重载另一个子类的onStop()。如果每次行为变更都通过继承来解决,那子类的数量就会越来越多(膨胀的子类)。更重要的是,添加增子类是在编译时新增行为, 有没有办法可以在运行时动态的修改行为?

2. 如何应对变化

通过将变化的行为封装在接口中,就可以实现动态修改:

//抽象行为
public interface Action{
    void doOnStart();
    void doOnStop();
}

public class Robot{
    //使用组合持有抽象行为
    private Action action;
    //动态改变行为
    public void setAction(Action action){
        this.action = action;
    }
    
    public void onStart(){
        if(action!=null){
            action.doOnStart();
        }
    }
    public void onStop(){
        if(action!=null){
            action.doOnStop();
        }
    }
}

//具体行为1
public class Action1 implements Action{
    public void doOnStart(){
        goWorkAt8Am();
    }
    public void doOnStop(){
        goHomeAt9Pm();
    }
}

//具体行为2
public class Action2 implements Action{
    public void doOnStart(){
        goWorkAt9Am();
    }
    public void doOnStop(){
        goHomeAt10Pm();
    }
}

//将具体行为注入行为使用者(运行时动态改变)
public class Company{
    public static void main(String[] args){
        Robot robot1 = new Robot();
        robot1.setAction(robot1);
        Robot robot2 = new Robot();
        robot2.setAction(robot2);
    }
}

策略模式将具体的行为和行为的使用者隔离,这样的好处是,当行为发生变化时,行为的使用者不需要变动。

Android 中的各种监听器都采用了策略模式,比如:

public class RecyclerView{
    //使用组合持有抽象滚动行为
    private List<OnScrollListener> mScrollListeners;
    
    //抽象滚动行为
    public abstract static class OnScrollListener {
        public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
        public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
    }
    
    //动态修改滚动行为
    public void addOnScrollListener(OnScrollListener listener) {
        if (mScrollListeners == null) {
            mScrollListeners = new ArrayList<>();
        }
        mScrollListeners.add(listener);
    }
    
    //使用滚动行为
    void dispatchOnScrollStateChanged(int state) {
        if (mLayout != null) {
            mLayout.onScrollStateChanged(state);
        }
        onScrollStateChanged(state);

        if (mScrollListener != null) {
            mScrollListener.onScrollStateChanged(this, state);
        }
        if (mScrollListeners != null) {
            for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
                mScrollListeners.get(i).onScrollStateChanged(this, state);
            }
        }
    }
}

列表滚动后的行为各不相同,所以使用抽象类将其封装起来(其实和接口是一样的)。

模版方法模式

1. 变化是什么

从严格意义上讲,模版方法模式并不能套用开篇的那句话:“它们增加了一层抽象变化封装起来,然后对抽象编程,并利用多态应对变化”。因为如果这样说,就是在强调目的是 “应对变化” 。但模版方法的目的更像是 “复用算法”,虽然它也有应对变化的成分。

对模版方法模式来说,变化就是算法的某个步骤,举个例子:

public class View{
    public void draw(Canvas canvas) {
        ...
        // skip step 2 & 5 if possible (common case)
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we’re done...
            return;
        }
        ...
    }
    
    protected void dispatchDraw(Canvas canvas) {

    }
}

节选了View.draw()方法中的某一片段,从注释中可以看出draw()定义了一个绘图的算法框架,一共有七个步骤,所有步骤都被抽象成一个方法,其中的变化在于,每个步骤对于不同类型的View都可能是不同的。所以为了让不同View复用这套算法框架,就把它定义在了父类中,子类可以通过重写某一个步骤来定义不同的行为。

模版方法模式一种常用的重构方法,它将子类的共用逻辑抽象到父类中,并将子类特有子逻辑设计成抽象方法供子类重写。

2. 对比

  • 模版方法模式 vs 工厂方法模式

从代码层面看,模版方法的实现方式和工厂方法模式几乎一样,都是通过子类重写父类的方法。唯一的不同是,工厂方法模式父类中的方法必须是抽象的,也就是说强制子类实现,因为子类不实现父类就无法工作。而模版方法模式父类中的方法可以是不抽象的,也就是说子类可以不实现,父类照样能工作。这种在父类中空实现的方法有一个专门的名字叫 “hook(钩子)” ,钩子的存在,可以让子类有能力对算法的流程进行控制,比如ViewGroup.onInterceptTouchEvent()

  • 模版方法模式 vs 策略模式

从产出来看,模版方法模式和策略模式是一个阵营的,因为他们产出的都是一组行为(算法),而工厂模式产出的是一个对象。但它们俩对算法的控制力不同,策略模式可以轻松的替换掉整个算法,而模版方法模式只能替换掉算法中的某个步骤。从代码层面来看,它们的实现方式也不同,策略模式使用组合,而模版方法模式使用继承。组合比继承更具有弹性,因为它可以在运行时动态的替换行为。

后续

这个系列会持续分享对其他设计模式的理解。

《Head First》是一本让我对设计模式理解升级的书,强烈推荐(本篇中工厂模式的例子摘自其中)。

相关文章

  • 那些看似不同其实一样的设计模式,让我用有一句话总结它们

    虽然不同的设计模式解决的问题各不相同,但从一个更高的抽象层次来看,它们通过相同的手段来实现相同的目的,用一句话总结...

  • 设计模式总目录

    写最好的设计模式专栏 【设计模式总结】对常用设计模式的一些思考(未完待续。。。) 一句话设计模式

  • OC 设计模式全解析(iOS平台)

    设计模式理解 设计模式一般会问你在项目中常用的设计模式有那些?等你说完之后会问你怎么用的,什么场景用的?它的优缺点...

  • 【设计模式】概念、原则及分类

    设计模式有啥用 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重...

  • 软件设计模式的原理及应用

    什么是软件设计模式 软件行业的人,大都听过GoF(Gang of Four)总结的23个设计模式,设计模式其实并不...

  • 一句话设计模式——快速掌握23种设计模式的核心思想(上)

    《一句话设计模式》的想法起源于前段时间为一个培训机构上课的时候,每讲完一个设计模式我都会用一句话总结。总结往往不是...

  • MVVM

    不同设计模式或者架构,是为了解决不同的问题而出现的,所以不要为了设计模式而设计模式.设计模式,是为了让APP的分层...

  • python下的单例模式

    设计模式是前人工作的总结和提炼,也就是工作中解决问题的套路,不同问题有不同的解决套路单例设计模式就是其中之一。单例...

  • 阿厝:合理的模式让它成为翘楚

    传统商业模式,行业不同,模式也不同,《众创理论》让不同的行业走上一样的模式。另外,其实创业的机会处处都有,哪怕是街...

  • Android 状态模式

    源码地址 介绍 状态模式中的行为是由状态来决定的,不同的状态有不同的行为。状态模式和策略模式的结构几乎一样,但它们...

网友评论

    本文标题:那些看似不同其实一样的设计模式,让我用有一句话总结它们

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