工厂、策略和桥接

本文主要横向地分析比较一下这三种设计模式。

注:本文的示例代码使用的语言为Java。

之所将这三种模式放在一起讲,是因为这三种模式都体现了架构设计中先”分而治之“然后”自由组合“的模块化思想,其中,工厂模式是创建型模式,策略模式是行为型模式,桥接模式是结构型模式。下面分别来讲一讲。

工厂模式

工厂模式是这种三种模式中最简单的,也是最容易理解的。之所以称为创建型模式,是因为它提供了一种创建对象的优化方案,在工厂模式中,我们不会对客户端暴露我们的创建逻辑,而是通过一个共同的接口来指向需要创建的对象。

工厂模式定义了一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

对于一种设计模式而言,最重要的是知道在什么情况下使用,以及这种模式的局限性,必要的时候,会将多种设计模式进行组合使用,所以重要的不是死记硬背设计模式的UML图或者示例代码,而是要深刻理解设计模式背后所蕴藏的设计思想,做到举一反三,灵活运用。

工厂设计模式很容易理解,但是具体应该何时使用需要拿捏,因为创建对象的方式有很多,直接调用构造方法和使用单例模式都可以创建对象,为什么需要多此一举搞一个工厂呢?工厂的建立是为了工厂内原本分散的对象创建过程和逻辑过程进行分类归置,如果代码只是一次性的,那确实大可不必如此周折,但是业务是不可能一成不变的,而为了在修改业务时尽量减少修改代码的工作量,就需要前期的设计。凌乱不堪的代码是很难进行维护的,从创造价值的角度上来讲,凌乱的代码创造价值的性价比更低,而且会带来加班、抱怨和健康的损失。

由于工厂模式非常简单,这里不再详述,也不再给出示例代码,只是简单地给出一个UML图供参考。这张图不必详细解释也能看出大概。

image

当然,没有十全十美的模式,工厂模式当然也不能例外。很显而易见的一个问题就是,和所有的设计模式一样,使用了工厂模式,需要多写很多代码,每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,但是这种增加说成缺点也不恰当,毕竟也是为了后面维护代码更方便,代码结构的艺术,就在于平衡和取舍。

策略模式

策略的模式的出现是为了解决多重条件判定的问题,也就是无尽的if...else...,有人可能觉得多写一点条件判定也没什么,能解决问题就好了,但是多重判定带来的维护困难反而是次要的,最主要的是它极容易产生Bug,而且很难排查,而且条件超过一定数量时,很可能根本不知道自己在写什么了,相信写过多重判定的人都深有体会。

策略模式所做的事情实际上是分拆,将每个条件下的逻辑独立成一个类,在类的成员方法中来实现判定后的逻辑,这样直观解释可能有点抽象,下面结合UML图和示例代码来解释一下。

image
public interface Strategy {
   public int doOperation(int num1, int num2);
}

public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}

public class OperationSubtract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}

public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

public class Context {
   private Strategy strategy;
 
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());    
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationSubtract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

理解策略模式的关键在于上面的Context类,和工厂模式的不同之处在于,策略模式并非是以创建对象为目的,而是为了在运行时改变类的行为,因为属于行为型模式,但它和工厂模式都提现了面向接口编程的思想。通过给Context的构造方法传入不同的接口类Strategy的不同实现,动态地改变了策略执行逻辑,可以这么理解:将条件判定改为构造的包含不同成员变量的Context对象,将条件判定后的逻辑改为了Strategy的不同实现。

桥接模式

桥接模式作为最后一个介绍的模式,比前两个都要复杂一点。桥接模式的使用场景,是一个业务系统存在多个维度的变化,这么说可能很抽象,举一个具体的例子:系统的目的是画一个图形,图形有两个属性,一个是形状,一个是颜色,最后的结果需要对这两个属性进行不同的组合,比如红色的正方形,蓝色的三角形。。。我们很容易想到的一个方案是,为每种形状提供所有颜色的版本(或者为每种颜色提供所有形状的版本),我们把图形作为父类,各种形状作为子类,各种形状的不同颜色作为子类的子类,但是这样的设计很容易带来类爆炸的问题。于是我们有了第二种方案:提供两个父类一个是颜色,一个是形状,颜色父类和形状父类两个类都包含了相应的子类,然后根据需要对颜色和形状进行组合,这样的设计很容易看出可以精简类的数量。那么具体该如何实现呢,这就是桥接模式要解决的问题。从刚才的描述来看,我们在描述一种结构设计,所以桥接模式属于结构型模式,它实现了抽象和实现之间的解耦,具体应该怎么理解这句话,请看下面的UML图。

image

理解这张图的关键在于抽象类Abstraction,抽象类是一种介于普通类和接口之间的存在,它既可以拥有已经实现了的方法,也可以拥有抽象方法,它天生就是用来作为普通类和接口之间联系的桥梁(悟出来桥接模式为啥叫桥接模式了没?)。这么说还很抽象,下面上具体代码。

public abstract class Shape {
    Color color;
 
    public void setColor(Color color) {
        this.color = color;
    }
    
    public abstract void draw();
}

public class Circle extends Shape{
 
    public void draw() {
        color.bepaint("正方形");
    }
}

public class Rectangle extends Shape{
 
    public void draw() {
        color.bepaint("长方形");
    }
 
}

public class Square extends Shape{
 
    public void draw() {
        color.bepaint("正方形");
    }
 
}

public interface Color {
    public void bepaint(String shape);
}

public class White implements Color{
 
    public void bepaint(String shape) {
        System.out.println("白色的" + shape);
    }
 
}

public class Gray implements Color{
 
    public void bepaint(String shape) {
        System.out.println("灰色的" + shape);
    }
}

public class Black implements Color{
 
    public void bepaint(String shape) {
        System.out.println("黑色的" + shape);
    }
}

public class Client {
    public static void main(String[] args) {
        //白色正方形
        Color white = new White();
        Shape square = new Square();
        square.setColor(white);
        square.draw();
        
        //黑色长方形
        Color black = new Black();
        Shape rectange = new Rectangle();
        rectange.setColor(black);
        rectange.draw();
    }
}

上面的代码可以看出,抽象类Shape很好地起到了桥接的作用,它用一个成员变量容纳了颜色这个可变维度,又通过抽象方法draw()的不同实现实现了形状这个可变维度,最后在客户端Client中进行不同的组合,极大了减少了类的数量,整个代码结构看上去简洁清晰,可扩展性和可维护性都很好。

桥接模式的实现对于开发者的对于系统的抽象理解和设计能力有一定的要求,同时要从多变而复杂的业务中准确分辨提炼出具体的不同的变化维度也不是一件容易的事,毕竟,真实的开发可不会尽都是实现一个红色的正方形这种简单清晰的业务,所以更需要对于业务系统多加思考,敲键盘前三思,将具体而复杂的业务抽象化,是每一个开发者应该刻意培养的能力。

总结

以上便是对三种模式的横向对比。“分而治之”是应对业务复杂多变的有效手段,技术应当服务于业务,在使用设计模式的时候不可生搬硬套,而应该在理解思想的基础上灵活运用,不然纵使费尽心思,反而会吃力不讨好。而这三种模式虽然是在面对不同业务场景时的不同解决方案,又是“思想统一”的,个中意思,还需自己体会。