软件构造 5-2 Design Patterns for Maintainability

    技术2022-07-10  219

    5.2 面向可维护性的设计模式

    一. 关于如何创建类的新实例 的模式

    1. Factory method pattern 工厂方法模式

      当 client 不知道要创建哪个具体类的实例,或者不想在 client 代码中指明要具体创建的实例时,用工厂方法。

      工厂方法:定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。

      例,如图所示:

    Product p = new ProductTwo();//一般的构造方法 Product p = new ConcreteTwo.makeObject();//工厂方法

      静态工厂方法:既可以在 ADT 内部实现,也可以构造单独的工厂类。可以直接调用,无需 new 。

      Open-Closed Principle (OCP) 对扩展的开放,对修改已有代码的封闭。

    2. Abstract factory pattern 抽象工厂方法

      抽象工厂模式:提供接口以创建一组相关/相互依赖的对象但不需要指明其具体。

    一个 UI 包含多个窗口控件,这些控件在不同的 OS 中实现不同一个仓库类,要控制多个设备,这些设备的制造商各有不同,控制接口有差异

      例:

      例:

    //抽象产品接口和具体产品类 //AbstractProduct public interface Window{ public void setTitle(String s); public void repaint(); public void addScrollbar(...); } //ConcreteProductA1 public class PMWindow implements Window{ public void setTitle(){...} public void repaint(){...} } //ConcreteProductA2 public class MotifWindow implements Window{ public void setTitle(){...} public void repaint(){...} } //抽象工厂接口和具体工厂类 //AbstractFactory public interface AbstractWidgetFactory{ public Window createWindow(); public Scrollbar createScrollbar(); } //ConcreteFactory1 public class WidgetFactory1{ //第一个具体产品的工厂方法 public Window createWindow(){ return new MSWindow(); } public Scrollbar createScrollbar(){A} } //ConcreteFactory2 public class WidgetFactory2{ //第二个具体产品的工厂方法 public Window createWindow(){ return new MotifWindow(); } public Scrollbar createScrollbar(){B} } //辅助类 public class GUIBuilder{ //Delegate 到抽象工厂类 public void buildWindow(AbstractWidgetFactory widgetFactory){ //使用抽象工厂类分别创建两个具体产品 Window window = widgetFactory.createWindow(); Scrollbar scrollbar = widgetFactory.createScrollbar(); window.setTitle("New Window"); window.addScrollbar(scrollbar);//把创建的两个具体产品实例组合起来(模式之外) } } //Client builder辅助类 GUIBuilder builder = new GUIBuilder(); AbstractWidgetFactory widgetFactory = null;//定义抽象工厂接口的实例 //根据要创建的“组合产品”的类型,构建不同的抽象工厂子类 if("motif") widgetFactory = new WidgetFactory2(); else widgetFactory = new WidgetFactory1(); builder.buildWindow(widgetFactory);//具体构建

      抽象工厂方法创建的不是一个完整产品,而是“产品族”(遵循固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的 object ,各产品创建过程对 client 可见,但“搭配”不能改变。

      本质上, Abstract Factory 是把多类产品的 factory method 组合在一起。使用抽象工厂方法是防止 client 由于不知道搭配造成的失误。

    二. 结构设计模式

    1. Proxy 代理模式

      某个对象比较“敏感”/“私密”/“贵重”,不希望被 client 直接访问到,故设置 proxy ,在二者之间建立防火墙。即先加载假的(引用),等到有用时再加载真的。

      如图所示:

      流程简化为:

      例:

    public interface Image { void display(); } public class RealImage implements Image { private String fileName; public RealImage(String fileName){ this.fileName = fileName; loadFromDisk(fileName); } @Override public void display() {...} //每次创建都要从磁盘装载,代价高 private void loadFromDisk(String fileName){...} } public class ProxyImage implements Image { private Image realImage; private String fileName; //但不需要在构造的时候从文件装载 public ProxyImage(String fileName){ this.fileName = fileName; } @Override public void display() { //如果display的时候发现没有装载,则再delegation if(realImage == null){ realImage = new RealImage(fileName); } realImage.display();//Delegate到原来的类来完成具体装载 } } //Client: Image image = new ProxyImage("pic.jpg"); image.display(); image.display();

      Adaptor 与 Proxy 的不同:

    Adaptor 消除不兼容,目的是 B 以客户端期望的统一的方式与 A 建立起联系。Proxy 目的:隔离对复杂对象的访问,降低难度/代价,定位在“访问/使用行为”

    三. 行为模式

    1. 观察者模式 Observer

      多对一需求(类似广播):

    “粉丝”对“偶像”感兴趣,希望随时得知偶像的一举一动粉丝到偶像那里注册,偶像一旦有新闻发生,就推送给已注册的粉丝(回调 callback 粉丝的特定功能)

      时序图:

      代码:

    public class Subject { private List<Observer> observers = new ArrayList<Observer>();//维持一组“对自己感兴趣的”对象 private int state; public int getState() {return state;} public void setState(int state) { this.state = state; notifyAllObservers();//在自己状态变化时,通知所有“粉丝” } //允许“粉丝”调用该方法向自己注册,将其加入队列,即建立 delegation 关系 public void attach(Observer observer){observers.add(observer);} private void notifyAllObservers(){ for (Observer observer : observers) { //callback 调用“粉丝”的 update 操作,向粉丝“广播”自己的变化实际执行 delegation observer.update(); } } } //“粉丝”的抽象接口 public abstract class Observer { protected Subject subject; public abstract void update(); } public class BinaryObserver extends Observer{ public BinaryObserver(Subject subject){ //构造时,指定自己的“偶像”subject,把自己注册给它这是相反方向的 delegation this.subject = subject; this.subject.attach(this); } //注意:这个方法是被“偶像”回调的 @Override public void update() { //当“偶像”有状态变化时,调用 subject.getState() 获取最新信息 System.out.println( "Binary String: " + Integer.toBinaryString(subject.getState())); //不同子类的“粉丝”,其行为可定制 } } public class ObserverPatternDemo { public static void main(String[] args) { Subject subject = new Subject();//偶像一枚 //粉丝三枚 new HexaObserver(subject); new OctalObserver(subject); new BinaryObserver(subject); //偶像有新闻了 System.out.println("First state change: 15"); subject.setState(15); System.out.println("Second state change: 10"); subject.setState(10); //并没有直接调用粉丝行为的代码!但其内部隐藏着对粉丝行为的 delegation } }

      Java 里已经实现了该模式,提供了 Observable 抽象类(直接派生子类即可,构造“偶像”,观察对象)。Java 提供了 Observer 接口(观察者),实现该接口,构造“粉丝”。

    2. Visitor 模式

      对特定类型的 object 的特定操作 ( visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被 visit 的类。   为 ADT 预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变 ADT 本身的情况下通过 delegation 接入 ADT 。

      本质上:将数据和作用于数据上的某种/些特定操作分离开来。

      Visitor ——唯一的双向委托设计模式。

      例:

    /* Abstract element interface (visitable) */ public interface ItemElement { public int accept(ShoppingCartVisitor visitor); } /* Concrete element */ public class Book implements ItemElement{ private double price; ... //将处理数据的功能 delegate 到外部传入的 visitor int accept(ShoppingCartVisitor visitor) { visitor.visit(this); } } public class Fruit implements ItemElement{ private double weight; ... int accept(ShoppingCartVisitor visitor) { visitor.visit(this); } } /* Abstract visitor interface */ public interface ShoppingCartVisitor { int visit(Book book); int visit(Fruit fruit); } //这里只列出了一种 visitor 实现 public class ShoppingCartVisitorImpl implements ShoppingCartVisitor { //这个 visit 操作的功能完全可以在 Book 类内实现为一个方法,但这就不可变了 public int visit(Book book) { int cost=0; if(book.getPrice () > 50){ cost = book.getPrice() - 5; }else cost = book.getPrice(); System.out.println("Book ISBN::" + book.getIsbnNumber() + " cost ="+cost); return cost; } public int visit(Fruit fruit) { int cost = fruit.getPricePerKg()*fruit.getWeight(); System.out.println(fruit.getName () + " cost = "+ cost); return cost; } } public class ShoppingCartClient { public static void main(String[] args) { ItemElement[] items = new ItemElement[]{ new Book(20, "1234"),new Book(100, "5678"), new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")}; int total = calculatePrice(items); System.out.println("Total Cost = "+total); } private static int calculatePrice ItemElement[] items) { //只要更换 visitor 的具体实现,即可切换算法 ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl(); int sum=0; for(ItemElement item : items) sum = sum + item.accept(visitor); return sum; } }

      Visitor 与 Iterator 的不同:

    迭代器:以遍历的方式访问集合数据而无需暴露其内部表示,将“遍历”这项功能 delegate 到外部的 iterator 对象。Visitor:在特定 ADT 上执行某种特定操作,但该操作不在 ADT 内部实现,而是 delegate 到独立的 visitor 对象,客户端可灵活扩展/改变 visitor 的操作算法,而不影响 ADT

      Strategy 与 Visitor 的不同:

    二者都是通过 delegation 建立两个对象的动态联系Visitor 强调是的外部定义某种对 ADT 的操作,该操作于 ADT 自身关系不大(只是访问 ADT ),故 ADT 内部只需要开放 accept(visitor)即可, client 通过它设定 visitor 操作并在外部调用。而 Strategy 则强调是对 ADT 内部某些要实现的功能的相应算法的灵活替换。这些算法是 ADT 功能的重要组成部分,只不过是 delegate 到外部 strategy 类而已。visitor 是站在外部 client 的角度,灵活增加对 ADT 的各种不同操作(哪怕 ADT 没实现该操作), strategy 则是站在内部 ADT 的角度,灵活变化对其内部功能的不同配置。

    四. 设计模式的对比

    1. 设计模式共性样式 1

    只使用“继承”,不使用委托核心思路: OCP/DIP依赖反转,客户端只依赖“抽象”,不能依赖于“具体”发生变化时最好是“扩展”而不是“修改”

      单继承树如 Strategy(传统型) 、 Proxy 、 Template 模式。

    1.1 Adaptor 模式

      适用场合:你已经有了一个类,但其方法与目前 client 的需求不一致。

      根据 OCP 原则,不能改这个类,所以扩展一个 adaptor 和一个统一接口。

      注:实际上Adaptor 和被适配的类可能不是同一个接口的继承。

    1.2 Proxy 模式

      适用场合:你已经有了一个类,但其方法与目前 client 的需求不一致。

      根据 OCP 原则,不能改这个类,所以扩展一个 adaptor 和一个统一接口。

    1.3 Template (模板)模式

      适用场合:有共性的算法流程,但算法各步骤有不同的实现典型的“将共性提升至超类型,将个性保留在子类型”

    2. 设计模式共性样式 2

      两棵“继承树”,两个层次的"delegation"。

      双继承树如设计模式:桥接模式、Iterator 、 Factory method 、 Abstract Factory 、 Obsever 、Visitor 等。

    2.1 Strategy 模式(又叫桥接模式)

    2.2 Iterator 模式

    2.3 Factory method

    2.4 Abstract Factory

    2.5 Observer 模式

    2.6 Visitor 模式

    Processed: 0.015, SQL: 9