软件构造 5-2 Design Patterns for Maintainability

5.2 面向可维护性的设计模式

一. 关于如何创建类的新实例 的模式

1. Factory method pattern 工厂方法模式

  当 client 不知道要创建哪个具体类实例,或者想在 client 代码中指明要具体创建的实例时,用工厂方法

  工厂方法:定义一个用于创建对象的接口,让其子类决定实例化哪一个类,从而使一个类的实例化延迟到其子类

  例,如图所示:

在这里插入图片描述

Product p = new ProductTwo();//一般的构造方法
Product p = new ConcreteTwo.makeObject();//工厂方法

  静态工厂方法:既可以在 ADT 内部实现,也可以构造单独的工厂类。可以直接调用,无需 new

  Open-Closed Principle (OCP) 对扩展的开放,对修改已有代码的封闭。

2. Abstract factory pattern 抽象工厂方法

  抽象工厂模式:提供接口以创建一组相关/相互依赖的对象但不需要指明其具体。

  • 一个 UI 包含多个窗口控件,这些控件在不同的 OS 中实现不同
  • 一个仓库类,要控制多个设备,这些设备的制造商各有不同,控制接口有差异

  例:

在这里插入图片描述

  例:

//抽象产品接口和具体产品类
//AbstractProduct
public interface Window{
	public void setTitle(String s);
	public void repaint();
	public void addScrollbar(...);
}
//ConcreteProductA1
public class PMWindow implements Window{
	public void setTitle(){...}
	public void repaint(){...}
}
//ConcreteProductA2
public class MotifWindow implements Window{
	public void setTitle(){...}
	public void repaint(){...}
}
//抽象工厂接口和具体工厂类
//AbstractFactory
public interface AbstractWidgetFactory{
	public Window createWindow();
	public Scrollbar createScrollbar();
}
//ConcreteFactory1
public class WidgetFactory1{
	//第一个具体产品的工厂方法
	public Window createWindow(){
		return new MSWindow();
	}
	public Scrollbar createScrollbar(){A}
}
//ConcreteFactory2
public class WidgetFactory2{
	//第二个具体产品的工厂方法
	public Window createWindow(){
		return new MotifWindow();
	}
	public Scrollbar createScrollbar(){B}
}
//辅助类
public class GUIBuilder{
	//Delegate 到抽象工厂类
	public void buildWindow(AbstractWidgetFactory widgetFactory){
		//使用抽象工厂类分别创建两个具体产品
		Window window = widgetFactory.createWindow();
		Scrollbar scrollbar = widgetFactory.createScrollbar();
		window.setTitle("New Window");
		window.addScrollbar(scrollbar);//把创建的两个具体产品实例组合起来(模式之外)
	}
}
//Client builder辅助类
GUIBuilder builder = new GUIBuilder();
AbstractWidgetFactory widgetFactory = null;//定义抽象工厂接口的实例
//根据要创建的“组合产品”的类型,构建不同的抽象工厂子类
if("motif")
	widgetFactory = new WidgetFactory2();
else
	widgetFactory = new WidgetFactory1();
builder.buildWindow(widgetFactory);//具体构建

  抽象工厂方法创建的不是一个完整产品,而是“产品族”(遵循固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的 object ,各产品创建过程对 client 可见,但“搭配不能改变

  本质上, Abstract Factory 是把多类产品的 factory method 组合在一起。使用抽象工厂方法是防止 client 由于不知道搭配造成的失误。

二. 结构设计模式

1. Proxy 代理模式

  某个对象比较“敏感”/“私密”/“贵重”,不希望client 直接访问到,故设置 proxy ,在二者之间建立防火墙。即先加载假的(引用),等到有用时再加载真的。

  如图所示:

在这里插入图片描述

  流程简化为:

在这里插入图片描述

  例:

public interface Image {
	void display();
}
public class RealImage implements Image {
	private String fileName;
	public RealImage(String fileName){
		this.fileName = fileName;
		loadFromDisk(fileName);
	}
	@Override
	public void display() {...}
	//每次创建都要从磁盘装载,代价高
	private void loadFromDisk(String fileName){...}
}
public class ProxyImage implements Image {
	private Image realImage;
	private String fileName;
	//但不需要在构造的时候从文件装载
	public ProxyImage(String fileName){
		this.fileName = fileName;
	}
	@Override
	public void display() {
		//如果display的时候发现没有装载,则再delegation
		if(realImage == null){
			realImage = new RealImage(fileName);
		}
		realImage.display();//Delegate到原来的类来完成具体装载
	}
}
//Client:
Image image = new ProxyImage("pic.jpg");
image.display();
image.display();

  AdaptorProxy 的不同:

  • Adaptor 消除不兼容,目的是 B 以客户端期望的统一的方式与 A 建立起联系
  • Proxy 目的:隔离对复杂对象的访问,降低难度/代价,定位在“访问/使用行为”

三. 行为模式

1. 观察者模式 Observer

  多对一需求(类似广播):

  • “粉丝”对“偶像”感兴趣,希望随时得知偶像的一举一动
  • 粉丝到偶像那里注册,偶像一旦有新闻发生,就推送给已注册的粉丝(回调 callback 粉丝的特定功能)

在这里插入图片描述

  时序图:

在这里插入图片描述

  代码:

public class Subject {
	private List<Observer> observers = new ArrayList<Observer>();//维持一组“对自己感兴趣的”对象
	private int state;
	public int getState() {return state;}
	public void setState(int state) {
		this.state = state;
		notifyAllObservers();//在自己状态变化时,通知所有“粉丝”
	}
	//允许“粉丝”调用该方法向自己注册,将其加入队列,即建立 delegation 关系
	public void attach(Observer observer){observers.add(observer);}
	private void notifyAllObservers(){
		for (Observer observer : observers) {
			//callback 调用“粉丝”的 update 操作,向粉丝“广播”自己的变化实际执行 delegation
			observer.update();
		}
	}
}
//“粉丝”的抽象接口
public abstract class Observer {
	protected Subject subject;
	public abstract void update();
}
public class BinaryObserver extends Observer{
	public BinaryObserver(Subject subject){
		//构造时,指定自己的“偶像”subject,把自己注册给它这是相反方向的 delegation
		this.subject = subject;
		this.subject.attach(this);
	}
	//注意:这个方法是被“偶像”回调的
	@Override
	public void update() {
		//当“偶像”有状态变化时,调用 subject.getState() 获取最新信息
		System.out.println( "Binary String: " + Integer.toBinaryString(subject.getState()));
		//不同子类的“粉丝”,其行为可定制
	}
}
public class ObserverPatternDemo {
	public static void main(String[] args) {
		Subject subject = new Subject();//偶像一枚
		//粉丝三枚
		new HexaObserver(subject);
		new OctalObserver(subject);
		new BinaryObserver(subject);
		//偶像有新闻了
		System.out.println("First state change: 15");
		subject.setState(15);
		System.out.println("Second state change: 10");
		subject.setState(10);
		//并没有直接调用粉丝行为的代码!但其内部隐藏着对粉丝行为的 delegation
	}
}

在这里插入图片描述

  Java 里已经实现了该模式,提供了 Observable 抽象类(直接派生子类即可,构造“偶像”,观察对象)。Java 提供了 Observer 接口(观察者),实现该接口,构造“粉丝”。

2. Visitor 模式

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

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

在这里插入图片描述

  Visitor ——唯一的双向委托设计模式。

  例:

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

  VisitorIterator 的不同:

  • 迭代器:以遍历的方式访问集合数据而无需暴露其内部表示,将“遍历”这项功能 delegate 到外部的 iterator 对象。
  • Visitor:在特定 ADT 上执行某种特定操作,但该操作ADT 内部实现,而是 delegate 到独立的 visitor 对象,客户端可灵活扩展/改变 visitor 的操作算法,而不影响 ADT

  StrategyVisitor 的不同:

  • 二者都是通过 delegation 建立两个对象的动态联系
  • Visitor 强调是的外部定义某种对 ADT 的操作,该操作于 ADT 自身关系不大(只是访问 ADT ),故 ADT 内部只需要开放 accept(visitor)即可, client 通过它设定 visitor 操作并在外部调用
  • Strategy 则强调是对 ADT 内部某些要实现的功能的相应算法的灵活替换。这些算法ADT 功能的重要组成部分,只不过是 delegate 到外部 strategy 类而已。
  • visitor 是站在外部 client 的角度,灵活增加对 ADT 的各种不同操作(哪怕 ADT 没实现该操作), strategy 则是站在内部 ADT 的角度,灵活变化对其内部功能的不同配置

四. 设计模式的对比

1. 设计模式共性样式 1

在这里插入图片描述

  • 只使用“继承”,使用委托
  • 核心思路: OCP/DIP
  • 依赖反转,客户端只依赖“抽象”,不能
  • 依赖于“具体”
  • 发生变化时最好是“扩展”而不是“修改”

  单继承树如 Strategy(传统型) 、 ProxyTemplate 模式。

1.1 Adaptor 模式

  适用场合:你已经有了一个类,但其方法与目前 client 的需求不一致。

  根据 OCP 原则,不能改这个类,所以扩展一个 adaptor 和一个统一接口。

在这里插入图片描述
  注:实际上Adaptor 和被适配的类可能不是同一个接口的继承。

1.2 Proxy 模式

  适用场合:你已经有了一个类,但其方法与目前 client 的需求不一致。

  根据 OCP 原则,不能改这个类,所以扩展一个 adaptor 和一个统一接口。

在这里插入图片描述

1.3 Template (模板)模式

  适用场合:有共性的算法流程,但算法各步骤有不同的实现典型的“将共性提升至超类型,将个性保留在子类型”

在这里插入图片描述

2. 设计模式共性样式 2

  两棵“继承树”,两个层次的"delegation"。

在这里插入图片描述

  双继承树如设计模式:桥接模式、IteratorFactory methodAbstract FactoryObseverVisitor 等。

2.1 Strategy 模式(又叫桥接模式)

在这里插入图片描述

2.2 Iterator 模式

在这里插入图片描述

2.3 Factory method

在这里插入图片描述

2.4 Abstract Factory

在这里插入图片描述

2.5 Observer 模式

在这里插入图片描述

2.6 Visitor 模式

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用 JavaScript 编写的记忆游戏(附源代码)   项目:JavaScript 记忆游戏(附源代码) 记忆检查游戏是一个使用 HTML5、CSS 和 JavaScript 开发的简单项目。这个游戏是关于测试你的短期 记忆技能。玩这个游戏 时,一系列图像会出现在一个盒子形状的区域中 。玩家必须找到两个相同的图像并单击它们以使它们消失。 如何运行游戏? 记忆游戏项目仅包含 HTML、CSS 和 JavaScript。谈到此游戏的功能,用户必须单击两个相同的图像才能使它们消失。 点击卡片或按下键盘键,通过 2 乘 2 旋转来重建鸟儿对,并发现隐藏在下面的图像! 如果翻开的牌面相同(一对),您就赢了,并且该对牌将从游戏中消失! 否则,卡片会自动翻面朝下,您需要重新尝试! 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox, 以获得更好、更优化的游戏体验。要玩游戏,首先,通过单击 memorygame-index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值