软件构造第五章总结—Part Ⅰ

前言:软件构造第五章学习笔记: Maintainability

Part Ⅰ Design Patterns for Maintainability

一.更好地创建类实例

1.工厂方法

解决的问题:client不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例

解决方法:定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从 而使一个类的实例化延迟到其子类
在这里插入图片描述
如上图所示,Product是用户想要创建的产品,它可能有多种类型,这里举例是两种类型,client创建时不想知道这些类型是什么,它只想获得一个产品,这时怎么办?(比如方便面有很多口味,但我不在意口味,只喜欢康师傅)

我们创建一个工厂接口,即图中的Creator,并提供一个创建产品的功能接口,返回的产品是抽象接口Product类型的,client只需与该工厂交互即可。

Product是一个抽象接口,定义了产品的规范,它的各个实现子类代表产品的各个种类,但是用户不想选择种类,只需要它是产品。因此,在工厂接口中创建产品的返回值类型定义为Product类型

接下来我们对应每一个产品都建立一个具体工厂来实现工厂接口,其中创建产品的功能就是new一个具体产品的实例并返回,如图中的工厂1(ConcreteCreatorOne)生产产品1(ProductOne),
在这里插入图片描述
工厂2(ConcreteCreatorTwo)生产产品2(ProductTwox)
在这里插入图片描述
这时用户在新建产品时只需指定工厂,调用工厂中的创建功能,就可以得到一个产品,client甚至可以不知道产品的种类是什么,如:
在这里插入图片描述
我买到了康师傅牛肉面(ConcreteCreatorOne),红烧牛肉面(ProductOne)还是酸菜牛肉面(ProductTwo)我不管,我就要康师傅!

另外,我们还可以令一个工厂类中自由选择创建多种产品,通过用户输入参数来确定创建哪一个,比如上面的例子中,我们用ConcreteCreatorOne创建两种产品:

public class ConcreteCreatorOne implements Creator{
	public Product factoryMethod(String type){
		if(type.equals("红烧")){
			return new ProductOne();
		}
		else if(type.equals("酸菜")){
			return new ProductTwo();
		}
	}
}

我想要康师傅红烧牛肉面,那么输入红烧就可以啦!

优点:
<1>用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程(像Date类有很多重载的构造函数,client如果不多做了解可能不知道要调用哪一个);
<2>代码的处理都是针对统一的Product,对用户自定义的任意一种具体产品都可适用

缺点:
<1>client需要具体化Creator(如上面的one和two)
<2>每增加一种产品就需要增加一个新的工厂子类

2.静态工厂

静态工厂与工厂方法并无直接关系,但二者解决的问题和解决的方法很相似,于是归纳在这里。
解决的问题:同1

解决的方法:
在ADT内部或工厂方法内部提供一个static的创建产品的方法,返回Product类型的产品,client每次只需要调用这个static函数即可。
如在ADT内部提供static创建方法:

public class ProductOne(){
	//得到红烧牛肉面
	public static Product getHongshao(){
		return new ProdcutOne();
	}
}

构造工厂类,并在工厂类内部提供静态方法

public class ConcreteCreatorOne(){
 	//得到康师傅红烧牛肉面
	 public static Product getKangshifu_Hongshao(){
 		 return new ProdcutOne();
 	}
}

相比于通过构造器(new)构建对象:
<1>. 静态工厂方法可具有指定的名称
<2>. 不必在每次调用的时候都创建新对象
<3>. 可以返回原返回类型的任意子类

3.抽象工厂

解决的问题:client想要创建一组产品,但不想单独创建一组中的每一个产品

解决方法:提供一个抽象接口,这个接口的每一个实现类都是一组产品,里面调用每一个产品的工厂方法,形成不同的组合。即每个实现类是一个产品组合,用户从所有组合中进行挑选。

我们来看下面的图:
在这里插入图片描述
AbstractWidgetFactory是一个抽象工厂,需要创建Window+Scrollbar的一组产品,在里面定义每一个产品的创建方法
WidgetFactory1是第一种实现搭配: MSWindow+Scrollbar A
WidgetFactory2是第二种实现搭配: MotifWindow+Scrollbar B
然后用户选择二者之一即可
在这里插入图片描述
如果client需要别的搭配,那么可以对其设计新的实现子类。

优点:
<1>当增加一个新的产品族时不需要修改原代码
<2>无需client自己逐个挑选产品,而是为用户搭配好以作选择
<3>当增加一个新的产品族时不需要修改原代码,满足开闭原则

缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改

二.结构型模式:代理

解决的问题: 某个对象比较“敏感”/“私密”/“贵重”,不希望被client直接访问到

解决方法:设置代理(proxy),在二者之间建立防火墙。
在这里插入图片描述
上面的图很清晰地展现了这个过程:
<1>首先,client有功能需求Request,比如他想实现面积计算功能,但完成这个功能的对象RealSubject是私密的/敏感的/贵重的,我们不想让client直接访问、调用
<2>于是,我们将这个功能需求抽象出来,形成Subject接口,私密对象RealSubject是其实现类。
<3>再设置一个实现类Proxy,里面的实现方式可通过委托给私密对象RealSubject来完成,并且可以增加实现细节,比如图中的preRequest、postRequest
亦或是对调用增加判断,比如这个私密对象只能调用一次,那么在委托之前可以判断调用次数,如果超过一次进行提示,直接返回,如图:

public class Proxy implements Subject{
	private RealSubject realSubject=new RealSubject();//成员变量,委托基础
	private boolean ifhasset=false;//记录是否调用过
	@Override
	public void Request(){
		if(ifhasset){
			System.out.println("该方法无法多次调用!");
			return;
		}
		realSubject.Request();//委托
		ifhasset=true;
	}
}

<4>client如果想要使用这个功能,通过Proxy进行调用与交互

	Proxy proxy=new Proxy();
	proxy.Request();

我们可以体会到,代理模式的目的就是隔离client对于某个对象的访问,因为直接访问是我们不希望的,比如可能不安全或是代价过大,而提供一个新的访问途径,而且这个途径满足了我们的要求,降低了访问难度/代价等

这个模式大致有三种使用环境:
<1>为一个对象在不同的地址空间提供局部代表,如计算机中的cache
<2> 虚代理:根据需要创建开销很大的对象 ,比如文件读写,我们提供一个外部磁盘内容装载的代理,如果直接令client访问,那么它可以非常轻易地使用磁盘装载,即使它不一定需要这个行为,那么我们建立代理以后,每次用户发出请求,我们先并不真正地装载,直到用户真正需要时再装载,从而降低总的访问代价
在这里插入图片描述
<3>区分访问权限,提供访问保护

优点:
<1>代理模式在客户端与目标对象之间起到一个中介作用,可以保护目标对象,降低访问权限等
<2>代理对象可以扩展目标对象的功能

缺点:在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢

三.行为型模式

1.观察者模式/发布—订阅模式

解决的问题:一方(“粉丝”)对另一方(“偶像”)感兴趣,希望随时得知另一方(偶像)的一举一动,需要得知另一方(偶像)的任何变化

解决方法: 粉丝到偶像那里注册,偶像一旦有新闻发生,就推送给已注册的粉丝
在这里插入图片描述
这样说可能还是很抽象,以上图为例说明这个模式的设计步骤:
Subject类是上面叙述中的“偶像方”,Observer是其一个“粉丝”,Observer需要及时得知所有Subject发生的变化

<1>Subject中维护一个Oberver列表,里面存储的所有Observer都是“注册的”,Subject的变化只需要通知这个列表中的所有Oberver.其中还提供两个方法attach和detach。

<2>每一个Oberver对象可以通过attach方法将自己加入到这个列表中,只要加入了这个列表之中,就可以使得自己收到Subject的所有变化信息

<3>当Oberver对象不再需要关注Subject的变化信息时,只需要通过detach方法将自己从列表中移除

<4>Subject中提供方法notify,当自身发生变化时调用该函数,以通知所有列表中的Oberver对象,具体的通知方法就是调用每个Oberver对象的update函数

<5>Oberver中提供update方法,以供Subject调用,当调用这个函数时,就说明Subject发生了变化,然后这个方法定义Oberver中在得知Subject发生变化后要采取的行为,一般可以在Subject中提供一个getState方法,方便在update中调用以得知Subject发生的具体变化是什么,以执行对应操作

优点:
<1> 主体(被观察者)和观察者之间松耦合,主体不需要关注观察者
<2> 动态的增加和删除观察者
<3> 观察者的行为不受主体的控制

缺点:
<1>主体需要存储观察者列表
<2>当观察者对象很多时,通知的发布会花费很多时间,影响效率
<3>依赖关系并没有完全解除,而且有可能出现循环引用

2.访问者模式

解决的问题:ADT拥有数据,但client对于数据会采取什么操作是未知的,或者client可能会对数据灵活地采用不同操作

解决方法:为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下在需要时通过delegation接入ADT,从而将数据和作用于数据上的某种/些特定操作分离开。
在这里插入图片描述
具体流程:
<1>Element是client要求扩展点的ADT,是我们要设计的ADT。ConcreteElementA 和ConcreteElementB是其两个实现子类

<2>在Element中保留一个扩展点:accept方法,其可以接受一个Visitor类型的参数,它的子ConcreteElementA 和ConcreteElementB在实现这个方法时只需委托给这个参数来完成,这就是为外部预留的扩展点。外部需要的操作只需要全部在Visitor中定义即可

<3>Visitor接口:定义一个visit方法,参数设置为Element类型,因此可以接受一个Element类型的对象,获取其保存的数据,然后利用这些数据完成所有client需要扩展的操作,从而实现对Element中的数据的扩展操作。

<4>Visitor的实现子类,这里是ConcreteVisitor1和ConcreteVisitor2,提供操作的多种具体实现,规定client“对ADT操作的具体方式”,当需要不同的方式就使用不同的子类

具体使用时就是ADT从Visitor的子类中选择一个实现方式,传入ADT的accept方法中,

	Element element=new Element();
	Visitor visitor1=new ConcreteVisitor1();
	element.accept(visitor1);

然后accept方法其实就是调用参数Visitor的visit方法,将自己作为参数传出去,从而令Visitor获取ADT数据,进行扩展

	public void accept(Visitor visitor){
		visitor.visit(this);
	}
	public void visit(Element e){
		//需要扩展的操作
		…………
		…………
	}

优点:
<1>能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能,扩展性好。
<2>访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地变化而不影响系统的数据结构,非常灵活。
<3>访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一,符合单一职责原则。

缺点:
<1>增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作。
<2>访问者模式中具体元素对访问者公布细节,暴露了内部实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值