通熟易懂的设计模式一

41次阅读

共计 5300 个字符,预计需要花费 14 分钟才能阅读完成。

写在前面

评判一个程序员是否优秀,就是 show me the code。优秀的代码可读性强,高内聚低耦合,可扩展。想要写优秀的代码,做个优秀的程序员,就需要多看看大牛写的开源框架,吸取其中的精华,多学学设计模式,除此之外,没有任何其他捷径。

设计模式主要分为 创建型模式 结构型模式 行为型模式 三种类型。

工厂方法(Factory method pattern)

定义一个创建对象的接口,让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行,它属于创建型模式。

工厂对象通常包含一个或多个方法,用来创建这个工厂所能创建的各种类型的对象。这些方法可能接收参数,用来指定对象创建的方式,最后返回创建的对象。

工厂通常是一个用来创建其他对象的对象。工厂是构造方法的抽象,用来实现不同的分配方案。

维基百科工厂方法的例子

// 定义了 Button 如何创建
public interface Button{}
// 实现了 WinButton 
public class WinButton implements Button{}
// 实现了 MacButton 
public class MacButton implements Button{}

// 创建 Button 的工厂类
public interface ButtonFactory {Button createButton();
}
// 真正创建 WinButton 的实现类,实现了 ButtonFactory
public class WinButtonFactory implements ButtonFactory {
    @Override
    public static Button createButton(){return new WinButton();
    }
}
// 真正创建 MacButton 的实现类,实现了 ButtonFactory
public class MacButtonFactory implements ButtonFactory {
    @Override
    public static Button createButton(){return new MacButton();
    }
}

抽象工厂模式(Abstract factory pattern)

将一组具有同一主题的单独的工厂封装起来。在使用中,使用方需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一方法的具体对象。它属于创建型模式。

抽象工厂就好像是对工厂方法的一种扩展,有个产品族的概念,也就是一堆产品。上面的工厂方法就一个产品(Button),而下面抽象工厂的例子里有两个产品(Button 和 Border)。

抽象工厂的优点:
具体产品从客户代码中被分离出来。
容易改变产品的系列。
将一个系列的产品族统一到一起创建。

抽象工厂的缺点:
在产品族中扩展新的产品是很困难的,它需要修改抽象工厂的接口。

维基百科抽象工厂的例子

public interface Button {}
public interface Border {}

public class WinButton implements Button {}
public class WinBorder implements Border {}

public class MacButton implements Button {}
public class MacBorder implements Border {}

public interface AbstractFactory {Button createButton();
    Border createBorder();}

public class WinFactory {
    @Override
    public static Button createButton() {return new WinButton();
    }
    @Override
    public static Border createBorder() {return new WinBorder();
    }
}

public class MacFactory {
    @Override
    public static Button createButton() {return new MacButton();
    }
    @Override
    public static Border createBorder() {return new MacBorder();
    }
}

构建模式(Builder pattern)

当构建一个复杂对象时,就可以使用建造者模式。它实际上就是传入一个参数,然后返回对象本身,方便下一个属性或者参数来构建。可以按需构建对象,可扩展性强。它属于创建型模式。

在 JDK 中,StringBuilder 类的 append() 方法就是一个很好的构建模式例子。

    // java.lang.StringBuilder
    @Override
    public StringBuilder append(CharSequence s) {super.append(s);
        return this;
    }

原型模式(Prototype pattern)

它的特点在于通过 复制 一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的 原型,这个原型是可定制的。它属于创建型模式。

原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效,它们实际上就是命名不一样的同类数据。

在 JDK 中,Object 类中的 clone() 方法就是典型的原型模式。

    // 在具体的实现类里可以直接重写 clone() 方法
    protected native Object clone() throws CloneNotSupportedException;

单例模式(Singleton pattern)

不管在任何时候,获取一个对象时只会返回同一个实例。通常我们在都读取配置文件时,文件里的内容是不变的,因此我们就可以使用单例模式来实现。它属于创建型模式。

单例模式很简单,只需要三步就完成,下面是 JDK 里的 Runtime 类实现的单例模式。

public class Runtime {
    // 1.new 一个私有的静态的 Runtime 实例
    private static Runtime currentRuntime = new Runtime();

    // 2. 返回当前应用的 Runtime 实例
    public static Runtime getRuntime() {return currentRuntime;}

    // 3. 私有化构造方法,不允许在其它类构造 Runtime 实例
    private Runtime() {}
}

适配器模式(Adapter pattern)

适配器模式也称作包装(wrapper),它属于结构型模式。

它的作用是把原本两个不兼容的接口通过一个适配器或者包装成兼容的接口,然后它们可以一起工作。例如我们的手机数据线,就好比是一个适配器,一端是 USB 接口,一端是 Type-C 或者 Lightning 接口,本来它们是不能一起工作的,但我们用这跟数据线(适配器)就可以让它们协同工作了。

它的优点:
1. 可以让任何两个没有关联的接口一起工作。
2. 提高了接口的复用。
3. 增加了接口的透明度。
4. 灵活性好。

它的缺点:
1. 过多地使用适配器,会让系统非常零乱,不易整体进行把握。
2. 由于 JAVA 只能继承一个类,所以最多只能适配一个适配者类,而且目标类必须是抽象类。

在 JDK 中,Arrays 类中的 asList(T… a) 方法就是适配器模式的例子,把一个数组转换为一个集合。

    public static <T> List<T> asList(T... a) {return new ArrayList<>(a);
    }

在 JDK 中,Collections 工具类的 list() 方法把枚举转集合。

    public static <T> ArrayList<T> list(Enumeration<T> e) {ArrayList<T> l = new ArrayList<>();
        while (e.hasMoreElements())
            l.add(e.nextElement());
        return l;
    }

当然,这上面两个例子是非常简单的,好像看不出来使用了任何的设计模式,跟我们平时使用的转换一模一样。这里只是一个理念的介绍,实际上,在使用中是用一个中间的 Adapter 类来做兼容或者包装的。

桥接模式(Bridge pattern)

将抽象与其实现分离,以便两者可以独立变化。它属于结构型模式。

当一个类经常变化时,面向对象编程的特性变得非常有用,因为可以用最少的关于程序的先验知识来容易地改变程序的代码。当类和它经常变化时,桥模式很有用。类本身可以被认为是抽象,类可以作为实现来做。桥模式也可以被认为是两层抽象。

它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

它的优点:
1. 抽象和实现分离开。
2. 优秀的扩展能力。
3. 实现细节对调用方透明。

它的缺点:
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,需要针对抽象进行设计与编程,加深编程难度。

这是个生产不同车辆和需要不同生产流程的例子。首先,定义一个车辆(Vehicle)的抽象接口,里面有个生产车辆的对应流程集合和一个生产的抽象方法。然后是自行车(Bike)和汽车(Car)对抽象接口的实现。然后定义一个生产车辆需要的流程(WorkShop),它有两个实现 ProduceWorkShop 和 TestWorkShop。最后,main 方法的代码就是对这个桥接模式的使用方式。

这种设计模式的好处是方便添加一种车巴士(Bus),只需要继承 Vehicle 类。也非常方便的添加一种生产流程喷漆(PaintWorkShop),只需要继承 WorkShop 类即可,扩展性很高。

// 车辆的抽象接口
public abstract class Vehicle {
    //
    protected List<WorkShop> workshops = new ArrayList<WorkShop>();
    public Vehicle() {super();
    }
    public boolean joinWorkshop(WorkShop workshop) {return workshops.add(workshop);
    }
    public abstract void manufacture();}

// 自行车的实现
public class Bike extends Vehicle {
    @Override
    public void manufacture() {System.out.println("Manufactoring Bike...");
        workshops.stream().forEach(workshop -> workshop.work(this));
    }
}
// 汽车的实现
public class Car extends Vehicle {
    @Override
    public void manufacture() {System.out.println("Manufactoring Car");
        workshops.stream().forEach(workshop -> workshop.work(this));
    }
}
// 生产车的抽象接口
public abstract class WorkShop {public abstract void work(Vehicle vehicle);
}
// 制造车的实现
public class ProduceWorkShop extends WorkShop {public ProduceWorkShop() {super();
    }
    @Override
    public void work(Vehicle vehicle) {System.out.print("Producing...");
    }
}
// 测试车的实现
public class TestWorkShop extends WorkShop {public TestWorkShop() {super();
    }
    @Override
    public void work(Vehicle vehicle) {System.out.print("Testing...");
    }
}
// 使用
public class Main {public static void main(String[] args) {
        // 生产一辆自行车
        Vehicle bike = new Bike();
        bike.joinWorkshop(new ProduceWorkShop());
        bike.manufacture();
        // 生产一辆汽车
        Vehicle car = new Car();
        car.joinWorkshop(new ProduceWorkShop());
        car.joinWorkshop(new TestWorkShop());
        car.manufacture();}
}

最后,单看设计模式例子是非常简单的,但有时候写代码时却用不上这些设计模式,这是一种写代码思维上的转变。也就是在写代码之前,我们需要根据业务场景思考,那种设计模式适合使用,记住使用设计模式的最终目的是代码可读性强,高内聚低耦合,可扩展。这是一种思维上的转变,多思考在动手,熟能生巧。

PS:
清山绿水始于尘,博学多识贵于勤。
微信公众号:「清尘闲聊 」。
欢迎一起谈天说地,聊代码。

正文完
 0