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);
  • }
  • }
arduino

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

  • 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);
  • }
  • }
java

这是原本要调用的类

  • 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);
  • }
  • }
routeros

装饰器模式 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() {
  • ...
  • }
  • ...
  • }
java

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

  • 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();
  • }
  • ...
  • }
cpp

若要实现堆的撤销功能,则新建一个类,继承装饰器,实现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
  • }
  • ...
  • }
java

其他类型的修饰同理。

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

  • 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");
  • }
  • }
arduino

也可以选择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.");
  • }
  • }
arduino

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

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

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

模板模式 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.");
  • }
  • }
arduino

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

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

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

迭代器模式 Iterator

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

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

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

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

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

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

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

-- EOF --

Comments