哈工大2022春软件构造学习记录(五)

本文介绍了软件设计模式的三大类:创建型、结构型和行为型模式,包括工厂方法、适配器、装饰器、策略、模板方法和迭代器模式。设计模式是为了解决常见问题而制定的可复用代码设计经验,通过继承或委托实现程序的扩展性和可靠性。文章提供了每个模式的简要解释、应用场景和代码示例。
摘要由CSDN通过智能技术生成

七大设计模式理解和总结

        设计模式一般指软件设计模式。 软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

        软件设计模式一共有23种,在我们课程以及本篇博客中将只涉及其中的七种,它们通常采用继承或委托(delegation)的方式使得程序易于扩展。

1、Creational patterns创建型模式

1.1 Factory Method pattern工厂方法模式

       通常的创建类的实例的方式是采用new关键字,这需要我们知道要创建实例的类的名字,不过在一些特定情况下,例如client不知道/不确定/不关心要创建哪个具体类的实例(回忆在Lab2中创建Graph<L>接口的实现类对象,client不关心这个Graph是怎样实现),或者不想在client代码中指明要创建的实例时,这样的方法就显得不那么合适。这时我们可以采用工厂方法来创建实例,定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。

       如何来实现的?

       Item(需被创建实例相关):假设有一批item,类别相同但是有些许差异。那么我们可以构造一个Item接口,由各种具体的类ConcreteItemx实现。

       Factory(工厂方法相关):为了实现工厂方法模式,我们构造一个ItemFactory接口,在这个接口中定义一个getItem方法,它返回一个Item实例,其他方法可以视需要自己决定。对于每一种具体的item即ConcreteItemx都要设计一个ConcreteItemxFactory类,实现ItemFactory接口,在重写的getItem方法中构造ConcreteItemx实例并返回。

还有另一种常用方式,就是只设计一个ItemFactory接口的实现类Factory,它的getItem方法接收字符串参数“ConcreteItemx”,根据这个参数它创建ConcreteItemx实例返回。

       Client(使用者):new一个特定的ConcreteItemxFactory对象调用它的getItem方法,注意得到的实例类型应是抽象接口Item而非具体类ConcreteItemx

       静态工厂方法:既可以在ADT内部实现(回忆Graph<L>的empty方法),也可以构造单独的工厂类,相比通过构造器创建对象,静态工厂方法可具有指定的更有意义的名称,不必在每次调用的时候都创建新的工厂对象,可以返回原返回类型的任意子类型。

下面的文章中提供了更为全面的介绍以及代码示例:

https://blog.csdn.net/varyall/article/details/82355964?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165512658116781683945979%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165512658116781683945979&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-82355964-null-null.142^v14^pc_search_result_control_group,157^v14^new_3&utm_term=%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4187

2、Structural patterns结构型模式

2.1 Adapter 适配器模式

       右下的这副图非常形象,有助于理解适配器模式,贴给大家。

适配器模式是用来解决类之间接口不兼容问题的一种模式,有继承式或委托式两类,类图结构如下:

解释一下,例如原本有一个方法method做加法a+b,参数是a,b两个加数,我想扩展一下,仍用这个method方法的基本实现,制作一个做减法a-b的方法,在新的方法中可以对b做一个取相反数的操作变成c (= -b)再调用method方法,这就实现了适配,怎么实现?

Item: 某个原来有的类,它拥有要被适配的method方法。

Adapter(适配器相关):设计一个抽象接口Adapter,声明适配后的新方法,它仍然叫做method,但是参数是a,c。构造它的实现类MethodAdapter,重写method方法:创造一个Item对象并调用它的method方法,传入的参数为a,-b。这是一个委托delegation的例子。

Client:创建一个Client类,它有一个成员变量是Adapter类型实例,注意要对抽象接口Adapter编程(也就是要构造Adapter类型的实例而不是声明为具体的MethodAdapter类型),在Client的method方法中通过委托调用对应Adapter的method方法。

下面的文章中提供了更为全面的介绍以及代码示例:

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

class Rectangle implements Shape{
	public 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();
	int x1 = 1,x2 = 2,y1 = 3,y2 = 4;
	public void display() {
		shape.display(x1, y1, x2, y2);
	}
}

2.2 Decorator 装饰器模式

装饰器模式很有特点,它通常用于对原本的ADT添加一些特性的任意组合,实际上是让ADT的方法拥有扩展的功能(而不是扩展ADT的方法种类),表现上通常是递归的、一层套一层的new。Decorator模式采用继承+委托(聚合式)实现,修饰功能主要体现在对通用操作的重写上。

我们把装饰器模式分成三部分来理解:

Item:接口,定义最基本的,被装饰物ConcreteItem拥有的、装饰物Decorator可以做修饰的操作。

ConcreteItem:起始的,要被修饰的最基本版本的对象,我们在其基础上增加功能。在这个类中,把通用的这些方法写好。

抽象类Decorator和它的子类ConcreteDecoratorX:

Decorator要implements之前定义的Item接口,它一般是个抽象类。很关键的一点是,Decorator要采用聚合委托,也就是说我们需要定义一个Item成员变量。Decorator的方法:构造方法,需要传入一个Item对象来初始化成员变量;对于接口中的通用方法,可以选择重写(委托给Item实现),也可以是抽象方法。

ConcreteDecoratorX是Decorator的子类,除了要写一个接受Item参数的构造器(调用父类构造方法)外,你可以在其中添加一些个性化的功能,最重要的是要进行对通用操作的重写——调用父类重写好的这个方法(super.method),这归根结底其实就是ConcreteItem这个基本对象的操作,然后再添加一些你想要的其他的操作,也就是对基本对象method方法的修饰;另一种情况是Decorator中你将所有通用操作都写成了抽象方法,那么就在ConcreteDecoratorX重写的通用方法中委托Item对象即可。

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

public class ArrayStack implements Stack {
	... //rep
	public ArrayStack() {...}
	public void push(Item e) {...}
	public Item pop() { ... }
}
//Decorator
public abstract class StackDecorator implements Stack {
	protected final Stack stack; //delegation
	public StackDecorator(Stack stack) {
		this.stack = stack; 
	}
	public void push(Item e) {
		stack.push(e); 
	}
	public Item pop() {
		return stack.pop(); 
	}
}
//ConcreteDecorator1
public class UndoStack extends StackDecorator implements Stack {
	private final UndoLog log = new UndoLog();
	public UndoStack(Stack stack) {
		super(stack); 
	}
	public void push(Item e) {
        super.push(e); 
		log.append(UndoLog.PUSH, e); //修饰的功能
	}
	public void undo() {
		//implement decorator behaviors on stack
	}
	...
}

Client:一层一层地new对象,最里层是最原始的对象,越往外则添加的修饰操作越晚实现。

Stack s = new ArrayStack();
Stack us = new UndoStack(s);
Stack ss = new SecureStack(new SynchronizedStack(us));
// 一层一层装饰

这是一个装饰器模式实例的类图。

3、Behavioral patterns 行为类模式

3.1 Strategy 策略模式

另一个形象的图,在右下角,有助于大家理解。

Strategy 策略模式什么时候用?

假设某个ADT中有一个方法叫做pay,实现支付功能,我们可以想到,支付可以用多种手段实现,例如信用卡支付、现金支付、扫码支付都可以,如果我想方便地在这几种支付方式中转换该怎么来实现?这时Strategy 策略模式就派上了用场,也就是:有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码里。解决方案是为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例。类图如下:

Strategy接口:定义有客户端需要实现的任务方法,按照上面给的例子就是pay方法。

ConcreteStrategyX:Strategy接口的实现类,是实现客户端需求任务的各种方法,按照上面给的例子就是CreditCardSrategy、PayPalStrategy等,它们可以有个性化的实现,重写pay方法。

Client:设置一个任务方法,接受一个Strategy参数,委托Strategy实现类中重写的个性化的任务方法实现不同的算法选择。按照上面的例子,使用者要传递给pay方法一个Strategy对象paymentWay,pay方法会通过paymentWay对象调用一个具体的pay方法这是一种依赖形式的委托。

//Strategy
interface PaymentStrategy{
	public void pay(int amount);
}
//ConcreteStrategyX
class CreditCardStrategy implements PaymentStrategy{
	//……
	@Override
	public void pay(int amount) {
		//……
	}
}
class PaypalStrategy implements PaymentStrategy{
	//……
	@Override
	public void pay(int amount) {
		//……
	}
}
//Client
class ShoppingCart{
	//……
	public void pay(PaymentStrategy paymentmethod) {
		int amount = calculateTotal();
		paymentmethod.pay(amount);
	}
}

3.2 Template Method 模板模式

模板模式比较简单,它的特点是步骤是相同的,但是每一个步骤的实现可以有不同的选择,共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。这就像一个模板一样,步骤是模板挖好的槽,我们往里填不同的东西。这样的特性和白盒框架很像,通常使用继承和重写实现模板模式。

Template抽象类:它的某个方法method包含n个步骤,抽象成n个依次调用的方法step1、step2、……stepn。这n个方法在Template抽象类中声明为抽象方法。(如有不需要多种实现的step直接在Template抽象类中写出共性实现即可,需要时将这个方法设置为final禁止子类重写)

ImplementationX子类:继承自Template抽象类,重写n个step抽象方法,为它们每一步赋予个性化的实现。

3.3 Iterator迭代器模式

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

我将迭代器模式分为两部分理解:

ConcreteAggregate:你希望能够提供统一遍历机制的ADT,它要实现Iterable<E>接口,E应为需要被遍历的类型,因此必须重写iterator方法(返回一个Iterator对象)。

CertainIterator:某个你自定义的迭代器类,它要实现Iterator<E>接口,E应为需要被遍历的类型,因此必须重写hasNext、next方法。我一般会将CertainIterator类作为ConcreteAggregate的内部类,这样方便访问ConcreteAggregate的成员变量,为了重写那两个方法我还会在ConcreteAggregate增加一个int值index。这里我研究不深,建议大家看看这篇文章深入了解。


3.4 Visitor 访问者模式

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

我将Visitor模式分为两大部分理解:

Item接口和它的实现类ConcreteItemX:Item接口中要定义一个accept方法,接收一个Visitor类型的对象参数visitor,这个accept方法就是让某个ConcreteItemX可以被外部访问到的一个接入点。在ConcreteItemX类中重写accept方法,只需要一行代码,就是委派visitor调用visit方法,将需被访问对象也就是自己(this)传递过去。这是一种依赖委托

Visitor接口和它的实现类ConcreteVisitorX:在Visitor接口中定义有重载的visit方法,接收一个ConcreteItemX对象参数,有多少种ConcreteItemX就重载多少个visit方法。在ConcreteVisitorX中,重写这些visit方法,实现对各种ConcreteItemX的不同操作。

我们发现一件很有意思的事情,在Visitor模式中,Visitor对象被传递到Item对象的accept方法中用作dependency delegation,而Item对象也通过visit方法被传递到了ConcreteVisitorX,这是Visitor模式的一个特点,也是与Strategy模式的一点差别。Visitor模式与Strategy模式都是通过delegation建立两个对象的动态联系,但是Visitor强调是的外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client通过它设定visitor操作并在外部调用;而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类而已。

总结起来,就是visitor是站在外部client的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。

这篇文章提供了代码示例:

https://blog.csdn.net/zoinsung_lee/article/details/82777748?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165512750116782248553136%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165512750116782248553136&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-82777748-null-null.142^v14^pc_search_result_control_group,157^v14^new_3&utm_term=visitor%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4187

4、共性样式

4.1 共性样式1

典型例子:Adapter、Template

4.2 共性样式2

典型例子:Strategy、Iterator、Factory Method、Visitor

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值