05月10, 2020

软件构造 - 面向复用的设计模式

面向复用的设计模式

结构型模式 Structural patterns

适配器模式 Adapter

用途:将某个类/接口转换为client期望的其他形式 例:客户端想要调用LegacyRectangle的display方法,但是接口不匹配,后两个参数客户端提供的是x2, y2,而不是w和h。

class LegacyRectangle {
    void display(int x1, int y1, int w, int h) {... }
}

class Client {
    public display() {
    new LegacyRectangle().display(x1, y1, x2, y2);
    }
}

可以新增适配器类解决这个问题,客户端调用这个适配器类的方法即可。

interface Shape {
    void display(int x1, int y1, int x2, int y2);
}

将其实现,方法中将x2, y2转换成了w, h

class Rectangle implements Shape {
    void display(int x1, int y1, int x2, int y2) {
        new LegacyRectangle().display(x1, y1, x2-x1, y2-y1);
    }
}

这是原本要调用的类

class LegacyRectangle {
    void display(int x1, int y1, int w, int h) {...}
}

客户端中,调用适配器类的方法即可适配接口

class Client {
    Shape shape = new Rectangle();
    public display() {
        shape.display(x1, y1, x2, y2);
    }
}

装饰器模式 Decorator

当类需要有不同特性的实现,又需要将这些特性组合起来使用时,可以使用装饰器模式实现

  • 为对象增加不同侧面的特性
  • 对每一个特性构造子类,通过委派机制增加到对 象上

缺点:

  • 不满足LSP
  • 若需要实现不同特性的任意组合,父类接口中需包含所有子类的方法

例:先实现一个最基础的堆

interface Stack {
    void push(Item e);
    Item pop();
}

public class ArrayStack implements Stack {
    ... //rep
    public ArrayStack() {...}
    public void push(Item e) {
        ...
    }
    public Item pop() {
        ...
    }
    ...
}

然后新建一个用于装饰的基础类,基础的两个方法通过委托实现。

public abstract class StackDecorator implements Stack {
    protected final Stack stack;
    public StackDecorator(Stack stack) {
        this.stack = stack;
    }
    public void push(Item e) {
        stack.push(e);
    }
    public Item pop() {
        return stack.pop();
    }
    ...
}

若要实现堆的撤销功能,则新建一个类,继承装饰器,实现Stack,并在其中添加新的undo方法。同样,基础的push, pop操作通过委托实现,不过要在push前将其记录下来。

public class UndoStack
extends StackDecorator
implements Stack {
    private final UndoLog log = new UndoLog();
    public UndoStack(Stack stack) {
        super(stack);
    }
    public void push(Item e) {
        log.append(UndoLog.PUSH, e);
        super.push(e);
    }
    public void undo() {
        //implement decorator behaviors on stack
    }
    ...
}

其他类型的修饰同理。

在使用时,将不同类型的特性一层一层套在一起

Stack t = new SecureStack(
    new SynchronizedStack(
    new UndoStack(s))

$\color{red}{注:使用这种方法需要在Stack接口中包含Undo, Secure, Synchronized中的所有方法,否则无法调用特性方法,也无法类型转换再调用}$

外观模式 Facade

Facade模式提供一个统一的接口来取代一系列小接口调用,相当于对复杂系统做了一个封装,简化客户端使用 例如在lab3中,要判断资源/位置是否冲突以及寻找前序项。若这些功能都由客户端实现的话,则会造成大量方法的调用,不美观也不好修改。可以通过facade模式,新建一个类来将这些操作封装在一起,统一接受客户端的请求执行操作。

行为类模式 Behavioral patterns

策略模式 Strategy

若有多种不同的算法实现同一个任务,则可以使用strategy模式使客户端动态切换算法。 为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例

例:购物时的付款方式,可以选则多种付款方式

public interface PaymentStrategy {
    public void pay(int amount);
}

可以选择信用卡付款

    public class CreditCardStrategy implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String dateOfExpiry;
    public CreditCardStrategy(String nm, String ccNum,
    String cvv, String expiryDate){
        this.name=nm;
        this.cardNumber=ccNum;
        this.cvv=cvv;
        this.dateOfExpiry=expiryDate;
    }
    @Override
    public void pay(int amount) {
        System.out.println(amount +" paid with credit card");
    }
}

也可以选择Paypal付款

public class PaypalStrategy implements PaymentStrategy {
    private String emailId;
    private String password;
    public PaypalStrategy(String email, String pwd){
        this.emailId=email;
        this.password=pwd;
    }
    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using Paypal.");
    }
}

在购物车结账时,我们就可以选择二者之一了。

例如传入的参数是PaypalStrategy类的,使用的就是Paypal付款。

public class ShoppingCart {
    ...
    public void pay(PaymentStrategy paymentMethod){
        int amount = calculateTotal();
        paymentMethod.pay(amount);
    }
}

模板模式 Template Method

  • 做事情的步骤一样,但具体方法不同
  • 共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现
  • 使用继承和重写实现模板模式

例:每天早上都要起床,晚上都要睡觉,中午都要恰饭,而每天的工作内容不同。

设计一天的模板如下

public abstract class OneDay{
    public LocalDate date;

    public final void getUp(){
        System.out.println("Get Up.");
    }
    public final void eat(){
        System.out.println("要恰饭的嘛。");
    }
    public abstract void work();
    public final void sleep(){
        System.out.println("Sleep.");
    }
}

在不同子类中重写work方法:

public class Study extends OneDay{
    @Override
    public void work(){
        System.out.println("Study.");
    }
}
public class TouchFish extends OneDay{
    @Override
    public void work(){
        System.out.println("摸了。");
    }
}

即可在模板上添加不同的工作内容。

迭代器模式 Iterator

客户端希望遍历被放入容器/集合类的一组ADT对象,无需关心容器的具体类型

也就是说,不管对象被放进哪里,都应该提供同样的遍历方式

需要实现Iterable接口与其中的iterator方法。

public interface Iterable<T> {
    ...
    Iterator<T> iterator();
}

并在类中内嵌一个类实现Iterator类,在其中实现hasNext, next, remove方法。

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

本文链接:http://blog.zireaels.com/post/patterns-for-reuse.html

-- EOF --

Comments