11 面向可复用性和可维护性的设计模式

11 面向可复用性和可维护性的设计模式,第1张

0.设计模式

A.创建模式 –关注对象创建过程
Factory方法模式 创建对象时不指定确切的类。

B.结构模式 –处理类或对象的组合
1.Adapter允许具有不兼容接口的类通过围绕现有类的接口包装自己的接口来协同工作

2.Decorator在对象的方法中动态添加/重写行为。

C.行为模式 –描述类或对象交互和分配责任的方式
Strategy允许在运行时选择一系列算法中的一个。
Template方法将算法的框架定义为抽象类,允许其子类提供具体行为。
Iterator按顺序访问对象的元素,而不暴露其底层表示。
Visitor通过将方法层次结构移动到一个对象中,将算法与对象结构分离。

为什么选择可重用设计模式?
设计实现了更改的灵活性(可重用性)
在修复旧问题时最大限度地减少新问题的引入(可维护性)
允许在初始交付后交付更多功能(可扩展性)。

设计模式:针对软件设计中给定上下文中常见问题的通用、可重用的解决方案。
OO设计模式除了类本身,更强调多个类/对象之间的关系和交互过程—比接口/类复用的粒度更大。

1.创建模式–关注对象创建过程:Factory方法模式

亦被称为“虚拟构造器”。

为创建对象定义一个接口,让子类决定初始化哪个类 ,将类初始化 延伸至子类。

当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。
定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。

工厂接口里给出创建方法,然后由若干具体类implement工厂接口,在具体类里分别实现创建接口,具体类里返回某种对象类型。使用时创建 具体类的对象,再调用里面的方法。而不是直接创建Trace的某个子类

当然也可直接用静态工厂方法,即省去了创建具体类对象 这一步骤

优势:
–无需将特定于应用程序的类绑定到代码中–代码只处理产品接口(Trace),因此它可以处理任何用户定义的具体产品(FileTrace、SystemTrace)

缺点:

客户可能需要创建Creator的子类 如果客户端必须对创建者进行子类化,那么这是可以接受的,但是如果不是这样,那么客户端必须处理另一个进化点。

2.结构模式 –处理类或对象的组合 1.Adapter 允许具有不兼容接口的类通过围绕现有类的接口包装自己的接口来协同工作 1.适配器意图:

将一个类的接口转换成客户希望的另外一个接口。
Adapter模式使原本由于接口不兼容而不能一起工作的类可以一起工作。
适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

2.Adapter模式的优缺点

§ 该模式的主要优点如下:

– 客户端通过适配器可以透明地调用目标接口。

– 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。

– 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
§ 其缺点是:

– 对类适配器来说,更换适配器的实现过程比较复杂。

3.引入例子

在这里,circle是我们需要的目标类(然后继承自shape),而xxcircle是我们现在有的类,我们需要在circle里定义xxcircle对象以及在具体circle方法里调用xxcircle的方法(所以UML图里,circle是指向xxcircle的,表明circle中用了xxcircle的变量方法,两者是组合关系

3.例子1

构建的组织树,所有员工在其中实现IEmployee接口。IEmployee接口有一个名为ShowHappiness()的方法。
§我们需要将现有Consultant插入组织树。Consultant类是adaptee(适配者),它有一个名为ShowSmile()的方法。
§可以通过添加额外的间接级别(即适配器对象)来协调这种不一致性。



适配器设计的2个模式是继承和委托,继承适配者的方法或者利用适配者的方法。上面的例子是继承,下面的例子是委托

4.例子2

Adapter是将两种不同的接口整合在一起。

通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。


Client使用了shape的display方法(client的display方法里就是在调用shape的display方法)两者是关联关系。rectangle是适配器,legacyRextangle是适配者。shape是接口,rectangle实现接口,里面的display方法就是在调用legacyrectangle的display方法

2.Decorator在对象的方法中动态添加/重写行为。 1.定义与特点

§ 装饰(Decorator)模式的定义: – 指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
§ 装饰(Decorator)模式的主要优点有:

– 采用装饰模式扩展对象的功能比采用继承方式更加灵活。

– 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
§ 其主要缺点是: – 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。

2.结构与实现

§ 通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有
静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如
果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象
,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,
这就是装饰模式的目标。下面来分析其基本结构和实现方法。

1.模式的结构 – 装饰模式主要包含以下角色。

• 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责
任的对象。
• 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色
为其添加一些职责。
• 抽象装饰(Decorator)角色:实现抽象构件,并包含具体构件的实例,可
以通过其子类扩展具体构件的功能。
• 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任

2.应用场景

​ 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
​ 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
​ 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
​ 装饰模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及
PrintWriter 等,它们都是抽象装饰类

3.实现

问题: 需要对对象进行任意或者动态的扩展组合
方案: 实现一个通用接口作为要扩展的对象,将主要功能委托给基础对象(stack),然后添加功能(undo,secure,…)以递归的方式实现

结果:decorator同时使用子类型和委托

Component接口:定义装饰物执行的公共 *** 作

ConcreteComponent 类是 起始对象,目的是在其基础上增加功能(装饰),将通用的方法放到此对象中。

Decorator抽象类是所有装饰类的基类,里面包含的成员变量component 指向了被装饰的对象。

(decorator和component是聚合关系,decorator里有component对象)

ConcreteDecorator类是可以添加特性的实际decorator类。您可以有任意ConcreteDecorator类,每个类都代表一个可以添加的特性

3 例子1

public interface IceCream 
{ //顶层接口
 void AddTopping();
}

public class PlainIceCream implements IceCream
{ 
 //基础实现,无填加的冰激凌
 @Override
 public void AddTopping() 
 {
 System.out.println("Plain IceCream ready for some 
 toppings!");
 }
}
                    
/*装饰器基类*/
public abstract class ToppingDecorator implements IceCream{
 protected IceCream input;
 public ToppingDecorator(IceCream i){
 this.input = i;            //由于基类冰淇淋实现了icecream接口,用户传参会传具体的基类冰淇淋,因而这里实际上这里的icecream相当于变成了基类冰淇淋plainicecream
 }
 public abstract void AddTopping(); //留给具体装饰器实现
}

/*具体装饰类*/
public class CandyTopping extends ToppingDecorator
{
 public CandyTopping(IceCream i) 
 {
 super(i);
 }
 public void AddTopping() 
 {
input.AddTopping(); //decorate others first,这里调用的即为基类冰淇淋plainicecream的addtopping方法
System.out.println("Candy Topping added!");
 }
}

public class NutsTopping extends ToppingDecorator{
 //similar to CandyTopping
}

public class PeanutTopping extends ToppingDecorator{
 //similar to CandyTopping
}
public class Client 
{
 //a是普通冰淇淋,b是candy冰淇淋,a传进b的构造方法里,b的input就是a,以此类推。
  // 最后d 调用addtopping方法时会首先调用d的input.addtopping方法,即c.addtopping,进入c中,调用c的input.addtopping方法即b.addtopping,以此类推,最后到a,打印那句话,退出到b,以此类推,
 public static void main(String[] args) {
 IceCream a = new PlainIceCream(); 
 IceCream b = new CandyTopping(a); 
 IceCream c = new PeanutTopping(b); 
 IceCream d = new NutsTopping(c); 
 d.AddTopping();
 
 //or
IceCream toppingIceCream = 
 new NutsTopping(new PeanutTopping(new CandyTopping(new PlainIceCream()
 )));
 toppingIceCream.AddTopping();
}

 /*
The result:
Plain IceCream ready for some toppings!
Candy Topping added!
Peanut Topping added!
Nuts Topping added!
*/

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

4.例子2


3 行为类型 –描述类或对象交互和分配责任的方式

Strategy允许在运行时选择一系列算法中的一个。
Template方法将算法的框架定义为抽象类,允许其子类提供具体行为。
Iterator按顺序访问对象的元素,而不暴露其底层表示。
Visitor通过将方法层次结构移动到一个对象中,将算法与对象结构分离。

1.strategy 整体地替换算法

针对特定任务存在多种算法,调用者需要根据上下文环境动态的选择和切换。

定义一个算法的接口,每个算法用一个类来实现,客户端针对接口编写程序。

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


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


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

public class ShoppingCartTest 
{
 public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
Item item1 = new Item("1234",10);
Item item2 = new Item("5678",40);
cart.addItem(item1);
cart.addItem(item2);
//pay by paypal
cart.pay(new PaypalStrategy("[email protected]", "mypwd"));
//pay by credit card
cart.pay(new CreditCardStrategy(Alice", "1234", "786", "12/18"));
 }
}

有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码里。需要为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例。



2 Template方法 将算法的框架定义为抽象类,允许其子类提供具体行为。

在父类声明一个通用逻辑

模板模式用继承+重写的方式实现算法的不同部分

策略模式用委托机制实现不同完整算法的调用(接口+多态)

框架实现了算法的不变性

客户端提供每步的具体实现

共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现

3 Iterator 迭代器模式

客户端希望遍历被放入容器/集合类的一组ADT对象,无需关心容器的具体类型 – 也就是说,不管对象被放进哪里,都应该提供同样的遍历方式

–抽象迭代器类定义遍历协议
–每个聚合类的具体迭代器子类
–Aggregate 实例创建迭代器对象的实例

–Aggregate 实例保留对迭代器对象的引用




自己的类实现iterable,在构造iterator的方法里返回iterator的一个实现类,然后这个实现类实现里面的iterator的各个方法

4 Visitor 访问者模式

对特定类型的object的特定 *** 作(visit),在运行时将二者动态绑定到一起,该 *** 作可以灵活更改,无需更改被visit的 类

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

在特定ADT上执行某种特定 *** 作,但该 *** 作不在ADT内部实现,而是delegate到独立的visitor对象,客户端可灵活扩展/改变visitor的 *** 作算法,而不影响ADT

/* Abstract element interface (visitable) */
public interface ItemElement 
{
 public int accept(ShoppingCartVisitor visitor);
}

/* Concrete element */
public class Book implements ItemElement
{
 private double price;
 ...
 int accept(ShoppingCartVisitor visitor)    
 //将处理数据的功能delegate到外部传入的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);
 }
 //只要更换visitor的具体实现,即可切换算法
 private static int calculatePrice(ItemElement[] items) {
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int sum=0;
for(ItemElement item : items)
 sum = sum + item.accept(visitor);
return sum;
 }
}

也就是说在这里,visitor要访问element(book,fruit),因而在element里开设accept,允许传进来visitor,然后利用element的数据进行visitor 的visit *** 作(在accept里写visitor.visit(this),this就指的是外部visitor想要访问的当前element)然后visitor接口针对不同element有不同visit方法,以及会有具体实现类(可以理解为不同visitor对数据有不同处理方法),各个实现类对各种element处理都不同(在各自实现类里分别实现对n种element的n种visit方法)因而只要更换visitor的具体实现,即可切换算法(例如在client的calculatePrice里定义visitor时改成new shoppingCartVisitorImpl2()

Visitor强调是的外部定义某种对ADT的 *** 作,该 *** 作于ADT自身关系不
大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client通
过它设定visitor *** 作并在外部调用。

而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。
这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类
而已。

§ 区别:visitor是站在外部client的角度,灵活增加对ADT的各种不同 ***
作(哪怕ADT没实现该 *** 作),strategy则是站在内部ADT的角度,灵
活变化对其内部功能的不同配置。

4设计模式的共性与差异 0.对于7种设计方法思路的归纳

Factory:定义工厂,里面有不同的创造Trace对象方法,由不同的工厂实现类分别实现某一种方法,客户使用时调用某个工厂实现类的他实现的那个方法即可(根据条件返回某个具体的Trace,这个其实属于委托);也可以使用静态工厂方法,不需要继承原始工厂。这样做避免了直接用多态定义Trace的类型,而是可以根据具体传进来的参数来进行选择创建

Adaptor:适配器实现目标接口,同时继承/委托适配者的方法来使用这个方法。客户调用适配器

Decorator:有一个接口里面有方法addtopping,有若干基类实现这个接口及addtopping,表示是原始方案,然后有装饰器抽象类也继承接口,里面有有参构造方法定义字段具体类型(this.input=i,表明是哪种icecream,这个字段是protected的)以及addtopping,在它底下又若干具体装饰类重写addtopping表示具体装饰方法,他们的有参构造方法里会调用父类的有参构造方法。客户使用时一层套一层,调用最后形成的对象的addtopping方法即可获得逐层输出效果

Strategy:对于不同客户(shoppingcart),他有不同策略可以选择,因而建立抽象类(里面有pay方法),有若干实现类实现它里面的pay方法,表示具体的策略,然后客户里面也有pay方法,需要传进来具体策略的对象,方法里包括额外的方法与策略的pay方法

Template:对于一个任务有若干步骤,抽象类定义出这些步骤,有若干类分别实现这些步骤(重写)。客户只需任务具体解决方案对象即可

Iterator:自己的类实现iterable接口,在构造iterator的方法里返回iterator接口的一个实现类,然后定义这个实现类,实现里面的iterator的各个方法

Visitor:visitor要访问element(book,fruit),因而在element里开设accept,允许传进来visitor,然后利用element的数据进行visitor 的visit *** 作(在accept里写visitor.visit(this),this就指的是外部visitor想要访问的当前element)然后visitor接口针对不同element有不同visit方法,以及会有具体实现类(可以理解为不同visitor对数据有不同处理方法),各个实现类对各种element处理都不同(在各自实现类里分别实现对n种element的n种visit方法)因而只要更换visitor的具体实现,即可切换算法(例如在client的calculatePrice里定义visitor时改成new shoppingCartVisitorImpl2()

1.使用继承:adaptor、template



2 使用委托: factory,strategy,iterator,visitor





欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1499061.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-25
下一篇 2022-06-25

发表评论

登录后才能评论

评论列表(0条)

保存