软件体系结构与设计模式
软件体系结构与设计模式
第零章 绪论
一、举例:房屋设计
- 房屋设计中,根据图纸可以建造一幢房子
- 特点:房子的形状是完全根据图纸建造出来的;房子是看得见,摸得着的
- 问题:在软件设计中,我们是否可以根据设计文档(尤其是设计类图)而精确地生产出来的软件呢?软件产品是否也是看得见,摸得着的呢?
- 回答a:是的
- 可以根据设计文档而精确地生产出软件
- 设计类图代表软件的逻辑结构
- 回答b:软件产品是看不见也摸不到的
- 回答a:是的
二、举例:软件(程序)设计的例子
- 三次层次架构包含3层
- 每一层都包含特定的功能
- 表示层 ==> 用户图形界面
- 应用层 ==> 业务逻辑都在这一层
- 数据库访问层 ==> 包含所有的数据库访问方法
- 各层之间有调用关系
- 每一层都包含特定的功能
三、软件设计的考量
-
软件设计要考虑很多因素
- 模块化(modularization) (本课程关注)
- 可扩展性 (extensibility) (本课程关注)
- 性能 (performance)
- 安全 (security)
-
软件设计要借鉴成功经验
- 软件设计模式 (software design patterns)
- 软件体系结构 (software architecture)
四、软件体系结构与设计模式的定义
01 软件设计模式的定义
-
Definition of a pattern:
“A pattern addresses a recurring design problem that arises in specific design situations and presents a solution to it”.软件设计模式的定义:
“设计模式处理一个特定设计情况下反复出现的设计问题,并且为其提供一个解决方案”。 -
软件设计模式适用于小范围的局部的程序设计
-
为什么要使用设计模式?
- 学习专家经验、为了交流、为了重复利用成功的设计、提供设计修改
02 软件体系结构的定义
-
Definition of a software architecture :
The software architecture of a program or computing system is the structure or structures of the system, which comprise
software components,
the externally visible properties of those components, and
the relationships among them.软件体系结构的定义:
程序或计算系统的软件架构是系统的一个或多个结构,包括:
软件组件,
外部可见性质,
组件之间的关系。 -
软件体系结构是软件的整体架构,用于描述整个软件系统的结构
-
软件体系结构举例(Java EE Architecture)
五、软件体系结构的重要意义
Three reasons why SA is important to large, complex, software-intensive systems.
软件体系结构(SA)对于大型复杂的软件密集型系统来说很重要的三个原因。
-
SA是交流工具
-
It is a vehicle for communication among stakeholders.
它是一种在项目参与者之间的一种交流工具
-
-
SA是最早的设计决策的代表
-
It is the representation of the earliest design decisions. It shows the tradeoffs.
---between performance and security,
---between maintainability and reliability, and
---between the cost of the current development effort and the cost of future developments in the architecture.tradeoffs:折衷,权衡
它是最早的设计决策的代表。它展现了一种折衷和权衡:
---在性能和安全之间
---在可维护性和可靠性之间
---在当前开发工作的成本和架构中未来开发的成本之间
-
-
SA是一个系统的可重复利用的,可转移的抽象
-
It is a reusable, transferable abstraction of a system. It
---can be applied to other systems exhibiting similar requirements and
---can promote large-scale reuse and software product lines.它是在一种可重复使用的、可转移的、抽象的系统,它
---可以应用于其他具有类似要求的系统,以及
---可以促进大规模重用和软件产品线
-
六、设计模式类别
01 创建型模式
Creational
- 创建型模式包括:
- 工厂方法模式
- 抽象工厂模式
- 创建型设计模式是解决对象创建机制的设计模式
- 该类设计模式试图根据具体的情况,以适当方式创建对象
02 结构型模式
-
结构型模式包括:
- 适配器模式
- 桥接模式
-
结构型设计模式的主要目的是将不同的类和对象组合在一起,形成更大或者更复杂的结构体
-
例如,形成复杂的用户接口或者复杂的账户数据接口。
-
注意,该模式不是简单地将这些类摆在一起,而是要提供这些类之间的关联方式。
03 行为模式
- 行为模式包括:
- 策略模式
- 行为模式关心算法和对象之间的责任分配。
- 它关心的不是仅仅描述对象或类的模式,而是要更加侧重描述它们之间的通信模式。
- 行为模式刻画了很难在运行时跟踪的复杂的控制流。该模式将软件开发者的注意力从控制流转移到对象相互关联的方式方面。
第一章 工厂方法模式(创建型模式)
一、工厂方法模式简介
01 工厂方法模式设计
问题:一个层次类包含多个子类,当我们试图调用某个类中的某个方法,却不知道选择调用哪个类时怎么办?
工厂方法模式设计(Version 1.0)
Main(){
if (usrChoice==1){
obCar =new Lincoln(m);
}
else if (usrChoise==2){
obCar =new Cadillac(m);
}
else if (usrChoise==3){
obCar =new Buick(m);
}
obCar.buyCar();
}
该设计的缺点:高耦合、难看的条件语句、客户程序需要知道服务类的全部细节
工厂方法模式设计(Version 2.0)
怎样改善1.0的设计?
使用工厂方法模式,将选择与实例化(初始化)的一个适当类的功能封装在一个专门的类的专门的称作工厂方法的方法中 ==> 委托
工厂方法(){
if (usrChoice==1)
obCar =new Lincoln(m);
else if (usrChoice==2)
obCar =new Cadillac(m)
else if (usrChoice==3)
obCar = new Buick(m)
return obCar;
}
02 工厂方法的功能
- 选择一个类
- 创建该类的对象
- 以超类的类型返回该对象
- 不做任何其它事情
03 工厂方法的特点
- 工厂方法的优点
- 清洗客户程序
- 隐藏初始对象的繁杂的细节(工厂方法实现初始化某类的机制)
- 客户类只知道哪种类型的对象被创建了,而不必知道哪个具体的子类被初始化了;
客户类只知道父类类型
二、简单工厂方法模式理论及设计实例
01 简单工厂方法模式
- 图示
- Creator(工厂类)
- 工厂模式的中心
- 包含业务逻辑
- 创建产品类对象
- Product(产品)
- Java接口或者Java抽象类
- 公共接口或者具体子类的超类
- Concrete product(具体产品)
- 实现接口Product或扩展抽象类Product的具体Java类
02 简单工厂方法模式举例
-
汽车保险介绍程序-用简单工厂方法模式设计
-
下面的类图表示一个设计
---使用简单的工厂方法模式查询不同类型车险的特征。
(使用简单工厂方法模式设计的汽车保险类) -
AutoInsurance(汽车保险):各种索赔保险政策的接口( 即describe() )
-
四个子类
- Body:Body Injur Liability (人身保险,你撞了别人)
- Collision:Collision Coverage (车碰撞险,你的车损坏了)
- Com:Comprehensive Coverage (综合险)
- Person:Person Injury Protection (驾驶员保险)
- 实现AutoInsurance的接口
-
PolicyProducer:一个带有工厂方法的工厂类
- 工厂方法:getAutoObj(String option)
- 工厂方法将创建并返回一个AutoInsurance类型的对象
- 实际上,它将根据输入的选择参数 “opt” 创建并返回一个实现类的对象
-
源码
-
public interface AutoInsurance { abstract String describe(); } public class Body implements AutoInsurance{ private String description; public String describe(){ description = " Body Injur Liability”; return description; } } public class Collision implements AutoInsurance { private String description; public String describe() { description = "Collision Coverage”; return description; } } public class Collision implements AutoInsurance { private String description; public String describe() { description = "Collision Coverage”; return description; } } public class Com implements AutoInsurance{ private String description; public String describe() { description = "Comprehensive Coverage”; return description; } } public class Person implements AutoInsurance { private String description; public String describe() { description = "Personal Injury Protection”; return description; } } public class PolicyProducer{ public static AutoInsurance getAutoObj(String opt){ // 根据参数不同创建不同对象 AutoInsurance policy = null; if(opt.compareTo("bodyInjure") == 0){ //参数1 policy = new Body(); } else if(opt.compareTo("collision") == 0){ //参数2 policy = new Collision(); } else if(opt.compareTo("com") == 0){ //参数3 policy = new Com(); } else if(opt.compareTo("personInjure") == 0){ //参数4 policy = new Person(); } return policy; // 以超类的类型返回 } }
-
用户图形界面的监听器代码
class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent ae) { if (ae.getActionCommand().equals(SHOW)) { String type = (String) getSelectedItem(); String option=""; if (type.equals(BODYINJURE)) { option=“bodyInjure"; } else if (type.equals(COLLISION)) { option="collision"; } ...... AutoInsurance ai = PolicyProducer.getAutoObj(option); // 创建对象额责任交给PolicyProducer String desc = ai.describe(); txtForInfo.setText(desc); } }
-
03 简单工厂方法模式的特点
- 简单工厂方法模式的优点
- 一些逻辑被放在了工厂类里
- 工厂类包含必要的逻辑,它可以决定要创建产品类的哪些实例以及这些实例什么时候被创建
- 客户类不用自己创建对象
- 客户类不必创建产品类对象
- 责任分离
- 简单工厂方法模式实现了责任分离
- 一些逻辑被放在了工厂类里
- 简单工厂方法模式的缺点
- 添加Product子类比较困难
- 在工厂类的层次结构中添加新的子类需要修改工厂类的源码
- 即,需要在工厂方法里面的代码再增加一个条件语句
三、工厂方法模式理论及设计实例
01 工厂方法模式
1、工厂方法模式类图
2、汽车保险介绍程序-用工厂方法模式设计
(1)下面的类图表示了工厂方法模式的设计,用于查询不同类型汽车保险的特性。
(2)汽车保险的例子
工厂层次类
-
接口类 PolicyProducer.
- 实现类 BodyFactory
- 实现类 CollisionFactory
- 实现类 ComFactory
- 实现类 PersonFactory
-
源代码
public interface AutoInsurance { abstract String describe(); } public class Body implements AutoInsurance { private String description; public String describe() { description = “ Body Injur Liability:“; return description; } } // 其余的产品子类的实现类似,故此处省略。 // 工厂方法无条件语句 public interface PolicyProducer { public AutoInsurance getAutoObj(); } public class BodyFactory implements PolicyProducer { public AutoInsurance getAutoObj() { return new Body(); } } public class CollisionFactory implements PolicyProducer { public AutoInsurance getAutoObj() { return new Collision(); } } // 其它两个工厂子类代码类似,故此处省略 public class FactoryMethodGUI extends JFrame { class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent ae) { if (ae.getActionCommand().equals(SHOW)) { String t = (String) cmbInsuranceType.getSelectedItem(); PolicyProducer pp=null; //创建工厂子类对象 if (t.equals(BODYINJURE)) { pp = new BodyFactory(); } if (t.equals(COLLISION)) { pp = new CollisionFactory (); } if (t.equals(PERSONINJURE)) { pp = new PersonFactory (); } if (t.equals(COMP)) { pp = new ComFactory (); } AutoInsurance ai = pp.getAutoObj(); //获得了产品子类对象 String desc = ai.describe(); } } } }
四、工厂方法模式的进一步讨论
01 简单工厂方法模式
02 工厂方法模式
03 简单工厂方法模式和工厂方法模式的比较
1、中心不同
简单工厂模式的中心是一个具体的工厂类
工厂模式的中心是抽象工厂类(或接口)
2、工厂方法不同,一个静态,一个动态
在简单工厂方法模式中,工厂方法是静态的和
在工厂方法模式中,工厂方法是动态的,并且分布在具体的工厂类中
3、工厂方法的可扩展性
如果向产品层次结构中添加了一个新产品,那么我们只需要向产品层次结构中添加一个具体子类,向工厂层次结构中添加一个子类。
没有必要修改抽象工厂和现有的具体工厂子类方法(工厂类的已有源代码不需要改变)
4、两个工厂方法返回类型都是超类类型
问题:是否需要修改Client类?
在简单工厂方法模式和工厂方法模式中,工厂方法返回Product类型的对象,而不是具体产品类的对象。
客户端不需要知道类型的类型(这样,客户端类就不知道到底哪个产品子类的对象被创建了)
5、工厂方法模式支持开闭原则,但是简单工厂方法模式不支持开闭原则⭐
(开闭原则:打开扩展,关闭修改)
04 何时使用工厂方法模式?
- 一个类包含大量的条件语句来创建对象(大量条件语句)
- 一个类使用它创建它的子类来指定对象
即,创建对象的任务分散在客户类的子类中,比较乱,可使用工厂方法模式将创建产品类的子类对象的责任分离出来 - 我们希望本地化所创建的类的知识
05 工厂方法模式的思想
客户类直接创建层次类的对象的情况。此时,
1)客户类选择创建某个子类对象,
2)并且对该对象的方法实施调用。
想法:
1)客户类委托工厂类创建层次类的某个子类的对象
2)客户类对返回的对象实施调用
1、简单工厂方法模式的实现
将参数p传递给工厂类,工厂方法根据此参数创建合适的类的对象,并且以Car类型返回给调用者
2、工厂方法模式的一种实现方法
优点:① 客户类更干净了;② 客户类调用工厂子类变得简单了
缺点:不符合开闭原则
第二章 抽象方法模式(创建型模式)
一、抽象工厂模式的引入
01 抽象工厂模式的主要例子
现在假设有多个结构相同的产品层次类
问题:我们是否仍然使用工厂方法模式?
一些想法
对于每个产品层次类,我们构造一个工厂层次类
下图中使用的三个创建者
注释:这不是很好,因为有太多的Creator类 —— 还有其他更好的解决方法吗?
新的解决方案——请注意以下产品结构相同
二、抽象工厂模式理论
01 抽象工厂模式类图
02 抽象工厂模式介绍
- 抽象工厂模式简介
- 抽象工厂模式采用与工厂方法模式相同的概念
抽象工厂创建者是一个类,它提供了一个接口来生成一组对象
在Java编程语言中,它可以实现为一个界面和一个抽象类
- 抽象工厂模式采用与工厂方法模式相同的概念
- 抽象工厂模式的组件
- 一族(相关的)层次类
工厂接口,带有几个实现子类(子类个数与每个产品层次类的个数相同)
每个工厂子类都负责创建所有的Product层次类的指定的子类对象
- 一族(相关的)层次类
03 抽象工厂模式使用
-
何时使用抽象工厂模式?
- 当客户对象想要创建一族层次类的某个子类对象,而不想知道到底是哪个子类对象被创建了。
- 当客户端对象想要创建一组相关的、依赖的类的一个实例,而不需要知道要实例化哪个具体的类时,使用抽象工厂模式。
-
如果不使用抽象工厂模式,将会发生什么?
- 如果我们不使用抽象工厂,那么在创建此类实例时,选择适当类所需的实现需要出现在所有地方。
需要使用大量的条件语句。
- 如果我们不使用抽象工厂,那么在创建此类实例时,选择适当类所需的实现需要出现在所有地方。
-
Client对象怎样使用抽象工厂模式?
- 回答:使用具体的工厂子类创建Product子类对象,而不必知道到底哪个子类对象被创建类。
- Client对象利用这些具体的工厂来创建对象,因此,不需要知道实际实例化的是哪个具体类。
04 工厂方法模式与抽象工厂模式的区别
1、产品不同
(1)对于工厂方法模式,产品是一个单一产品类层次结构
(2)对于抽象工厂模式,产品是一组产品类层次结构
2、有关可扩展性
(1)工厂方法模式遵循开闭原则,同时
(2)抽象工厂模式只部分遵循开闭原则
2.5、关于开闭原则——两种情况
-
Case1
- 如果你添加一个ProductA3和ProductB3,也是创造者类层次结构需要添加类CreatorC。在此情况下,抽象工厂模式满足开闭原则
-
Case2
- 如果你添加了一个新产品 hierarchy ProductC,然后你需要在ConcreteCreator1,ConcreteCreator2添加一个新方法“+getObjC: ProductC”
- 这意味着您需要修改现有的工厂类层次结构,因此它不遵循开闭原则
- 层次结构:层次结构
三、抽象工厂模式设计实例
01 实例描述
让我们设计一个web应用程序来查询不同类型建筑的特征。
考虑两种类型的建筑:住宅和共管公寓。
此外,建筑可以是任何一种——Super or Medium
因为House和Condo代表了两种产品,每一种产品都可以分为super和medium,所以我们可以使用下面的抽象工厂模式
02 实例分析
-
设计原因
- 如果我们不使用抽象工厂模式,而只是直接调用类House和Condo中的方法,
然后在ClientGUI中,我们需要编写大量的条件语句,这不利于扩展和维护
- 如果我们不使用抽象工厂模式,而只是直接调用类House和Condo中的方法,
-
设计好处
-
责任分离
选择和实例化适当的House/Condo实现者的责任可以从应用程序对象转移到单独的指定抽象工厂类。
-
关于建筑工厂类的层次结构
它只声明所需的接口,而将类选择和实例化的实际实现细节留给其实现者。
-
-
关于client对象AbstractFactoryGUI类:
它使用实现BuildingFactory接口的具体工厂实例来创建对象 表示不同类型和类别的房子,而不需要知道需要实例化的实际具体类。
Client对象不需要知道这些具体工厂类的存在。
-
AbstractFactoryGUI如何工作?
- 当搜索按钮被点击之后,一个建筑物类别和类型组合被选中,客户端AbstractFactoryGUI调用在抽象BuildingFactory类中的静态的getBuildingFactory(String type)方法
- getBuildingFactory(String type) 方法创建了一个适当的工厂对象并且返回它作为一个BuildingFactory类型的对象。
- 工厂对象在内部从它控制的建筑家族(SuperHouse/SuperCondo或MediumHouse/MediumCondo)中创建一个适当类的实例,并将其作为House/Condo类型的对象返回。
- Client类AbstractFactoryGUI甚至不知道House/Condo的存在
-
client AbstractFactoryGUI 不需要不同的具体的House/Condo类。
- 它只是调用House/Condo接口声明的方法,比如: getHouseInfo or getCondoInfo
03 实例源代码
public interface House {
public String getHouseInfo();
}
public class SuperHouse implements House {
public String getHouseInfo() {
return "superHouse.html";
}
}
public class MediumHouse implements House{
public String getHouseInfo() {
return "mediumHouse.html";
}
}
public interface Condo {
public String getCondoInfo();
}
public class SuperCondo implements Condo{
public String getCondoInfo(){
return "superCondo.html";
}
}
public class MediumCondo implements Condo{
public String getCondoInfo(){
return "mediumCondo.html";
}
}
public abstract class BuildingFactory{
public static final String SUPER = "Super Class";
public static final String MEDIUM = "Medium Class";
public abstract House getHouse();
public abstract Condo getCondo();
public static BuildingFactory getBuildingFactory(String type){
BuildingFactory bf = null;
if (type.equals(BuildingFactory.SUPER)){
bf = new SuperBuildingFactory();
}
else if (type.equals(BuildingFactory.MEDIUM)){
bf = new MediumBuildingFactory();
// 真奇怪,在超类中创建子类对象。你可曾遇到过?
}
return bf;
}
}
public class MediumBuildingFactory extends BuildingFactory {
public House getHouse(){
return new MediumHouse();
}
public Condo getCondo(){
return new MediumCondo();
}
}
public class SuperBuildingFactory extends BuildingFactory {
public House getHouse(){
return new SuperHouse();
}
public Condo getCondo(){
return new SuperCondo();
}
}
public class AbstractFactoryGUI extends JFrame {
class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals(AbstractFactoryGUI.SEARCH)) {
String clas = (String) cmbHouseClass.getSelectedItem();
String type = (String) cmbHouseType.getSelectedItem();
BuildingFactory bf = BuildingFactory.getBuildingFactory(clas);
if (type.equals(AbstractFactoryGUI.HOUSE)) {
House hs = bf.getHouse();
String fileNm = hs.getHouseInfo();
putHouseInfoToScreen(fileNm);
}
if (type.equals(AbstractFactoryGUI.CONDO)) {
Condo cd = bf.getCondo();
String fileNm = cd.getCondoInfo();
putHouseInfoToScreen(fileNm);
}
}
}}
第三章 适配器模式(结构型模式)
一、类的接口
01 java.lang.Math类的接口声明
-
字段汇总(Field Summary)
-
类型 名称 描述 static double E 最接近自然对数底的double值 static double PI 最接近圆周率(周长与直径之比)的double值
-
-
方法汇总(Method Summary)
-
类型 名称 描述 static double cos(double a) 返回一个角的三角余弦值 static double exp(double a) 返回欧拉数e的次方的幂的double值 static double log(double a) 返回自然对数(以e为底)的double值 static double sin(double a) 返回一个角的三角正弦值
-
02 什么是类的接口?
-
类的所有暴露给外界的方法的全体
-
类的接口提供外部视图
二、适配器模式介绍
01 适配器模式引入
问题:有两个水管,一个比另一个细
怎么把两个水管连接起来?
不兼容的接口
解决:使用一个适配器让一个接口适配另一个接口
转换了接口,从而可以将粗细不同的两个水管连接起来。
在软件设计中,我们也经常会遇到类似的接口不一致的问题
02 举例:椭圆的接口转换
-
举例01:椭圆的接口转换
- Java API
- +Ellipse(int x, int y, int w, int h)
- 椭圆所在最小矩形的左上角坐标设为UL点
- 椭圆(UL点的横坐标,UL点的纵坐标,长轴,短轴)
- 我们希望以如下的方式声明椭圆
- Ellipse e = new Ellipse (cx, cy, a, b);
- 椭圆(中心点横坐标,中心点纵坐标,半长轴,半短轴)
- 问题:Java API中的构造方法与我们现在想要的构造方法不相容(不一致)
- 转换
- MyEllipse类通过centerX, centerY, a, b完成初始化
- 在MyEllipse的构造函数内部,调用类Ellipse的构造函数
- 客户类只需要使用构造方法 MyEllipse e = new MyEllipse (100, 50, 80, 40); 即可得到相应椭圆
- Java API
03 举例:现有类的功能不足
-
举例02:现有类的功能不足
- Adaptee( operation1():void )
- 问题:我们想要在一个名为Adaptee的现有类中使用operation1(),而且我们还需要另一个操作operation2(),它不在Adaptee类中。
- 解决方案1:修改类Adaptee以添加方法operation2()
- 缺点1:没有源代码
- 缺点2:即使有源代码,也不是一个好做法
- 即使有源代码,在修改文件之后,也需要重新编译它,这可能会导致一些新的问题
- 缺点3:如果这个类已经被其它应用程序使用,也要考虑改变之后的副作用
- 解决方案2:使用类Adapter继承Adaptee,并添加operation2()方法
- 点评:这是1990年代的做法;这是一种传统的通过继承增加功能的方法。然而,这种设计不利于扩展
- 解决方案3:使用一个叫做Target的新接口,声明所需要的所有方法,并且使用一个叫做Adapter的类,以便实现Target类的所有方法
- 这是类适配器设计模式
三、类适配器模式的新增功能情况
-
增加新功能时候的类适配器模式-示意图
- operation():void
- 需要在这里编写代码来实现operation2()。在为operation2()编写代码后,可以同时使用operation1()和operation2()。
-
增加新功能的示意性代码
-
public class Adaptee { //原接口 public void operation1() { System.out.print(“ This is an existing method.”) } } public interface Target { //新接口,声明所有的方法 void operation1(); void operation2(); } //适配器类 public class Adapter extends Adaptee implements Target { /* Class Adaptee doesn't contain Operation2(), so we need to implement it here. */ public void operation2() { // 写代码实现此方法 } //不用在Adapter类里面声明operatiion1(); }
-
怎样写Client类里的代码?
public class Client { private static Target adp; // use the interface as type public static void main (String[] args){ adp = new Adapter(); adp.operation1(); adp.operation2(); } }
-
-
问题:在类适配器模式中能否同时适配两个类?
如果有两个现有的类Adaptee1和Adaptee2,我们还能像下面这样使用类适配器模式吗?
- 解答:
- 在C++中,该设计可行,但是多继承有时可能会带来复杂性
- 在Java中,该设计不被允许,因为java不允许多继承
四、对象适配器模式的新增功能情况
-
增加新功能的时候的类适配器模式-示意图
-
对象适配器模式示例源代码
-
public class Adaptee { public void operation1() { System.out.println(“ operation 1 is in Adaptee.”); } } public interface Target { void operation1(); void operation2(); }
-
怎么写Adapter类?
public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) {//由参数传入 this.adaptee = adaptee; //adaptee对象 } public void operation1() { adaptee.operation1(); //调用 } public void operation2() { // 写新代码 System.out.print(“ you need to write code for operation2 .”); } }
-
-
在对象适配器模式中能否同时适配两个类?(可以)
五、适配器模式的一般形式
01 一般形式的类适配器模式
-
一般形式的类适配器模式类图(Gof)
- 我们欲使用Adaptee中的方法,但是接口不是我们想要的,我们想要新接口中的方法。使用Adapter进行转换。
-
一般形式的类适配器模式解释
- Adaptee中包含了方法specificRequest(),实际上,可以包含多个方法
- 在Target类中;声明你所有想要的方法request(). 这里,Target可以包含多个方法。
- 在Adapter 中,实现Target类中声明的所有方法
- Adapter 实现Target接口类
- Adapter 继承Adaptee类
02 一般形式的对象适配器模式
-
一般形式的对象适配器模式类图
- 我们欲使用Adaptee中的方法,但是接口不是我们想要的,我们想要新接口中的方法。使用Adapter进行转换。
-
一般形式的对象适配器模式解释
- Adaptee中包含了方法specificRequest(),实际上,可以包含多个方法
- 在Target类中;声明你所有想要的方法request(). 这里,Target可以包含多个方法。
- 在Adapter 中,实现Target类中声明的所有方法
- Adapter 实现Target接口类
- Adapter 调用Adaptee类
-
在使用适配器模式的时候Client类的代码
-
Target tgt; // ==> 面向接口编程 tgt = new Adapter(); // ==> tgt.operation1(); tgt.operation2();
-
原因:一个Target接口可能有多个不同的Adaper
-
-
有关适配器模式的讨论:哪种情况下使用(类、对象)适配器模式?
- 你想使用一个已经存在的类,但是该类的接口不是你想要的。
- 你想创建一个可复用的类,该类与一些互不相关的接口不相容的类进行合作或者你要改变许多子类的接口。这种情况下,你可以使用对象适配器模式。
六、适配器模式设计实例
01 举例:离架软件,功能不足,欲增加新功能
-
概述
- 假设我们已经购买了一个用于验证客户信息的现成类InfoValidator(没有源代码)
功能包括:验证用户名、验证地址、验证区号、确认手机号码
- 假设我们已经购买了一个用于验证客户信息的现成类InfoValidator(没有源代码)
-
需求
- 上面的类提供了我们所需要的功能,然而,我们仍然需要另一个函数来验证社会安全号码(格式ddd-dd-dddd)。
类CusInfoValidator不包含这个函数,所以我们需要自己编写它。
在这种情况下,我们可以使用类适配器模式
- 上面的类提供了我们所需要的功能,然而,我们仍然需要另一个函数来验证社会安全号码(格式ddd-dd-dddd)。
-
设计
- 在这个设计中,我们需要的方法都包含在接口CusInfoValidator中
- isValidName( ): boolean
isValidAddress(): boolean
isValidZipCode(): boolean
isValidCellPhoneNum(): boolean
isValidSSNNum(): boolean
- isValidName( ): boolean
- 设计类图的解释
- 前4个方法包含在现成的类InfoValidator中。但是最后一个方法isValidSSNNum()没有包含在InfoValidator中。
InformationAdapter负责实现isValidSSNNum()
因为InformationAdapter已经继承了InfoValidator,所以前4个方法已经实现了
- 前4个方法包含在现成的类InfoValidator中。但是最后一个方法isValidSSNNum()没有包含在InfoValidator中。
02 举例:简单的改变接口问题
-
举例:简单的改变接口问题(1)
-
举例:简单的改变接口问题(2)
-
举例:改变接口问题-椭圆的例子
03 举例:改变接口问题-客户地址验证问题
假设一个以美国客户为主的电商网站也为加拿大客户服务。已经设计实现了两个类:
问题:接口不相容。
领导决定客户类Customer类访问一个唯一的接口,验证美国地址与加拿大地址。
使用类适配器模式:使用一个适配器改变 CAAddress 的接口
(1)需要设计一个名为AddressValidator的Java接口
(2)需要一个适配器类CAAddressAdapter——继承CAAddress类、实现AddressValidator接口
在Customer中怎样使用该模式?
AddressValidater av;
if(user chooses US)
av = new USAddress();
else if (user chooses Canada)
av = new CAAddressAdapter();
av. isValidAddress(addr, pcode, state);
也可以使用对象适配器进行设计
七、进一步讨论
01 类适配器模式和对象适配器模式的比较
图示比较
适配器模式的应用主要体现在两个方面
(1)改变接口
(2)增加功能
问题:这两个方面哪个是主要的呢?
回答:改变接口。实际上增加功能也可以看作是改变接口,因为增加了功能也就改变了接口。
类适配器模式和对象适配器模式的区别
(1)在类适配器模式中,所有属性和方法都是继承的
(2)在对象适配器模式中,通常只选择一个或几个方法来拉入适配器类
讨论:为什么在适配器模式中,为什么将Target设计为一个Java接口类?
回答:在适配器模式中,使用接口Target,原因是一个接口可以被多个子类实现;例如,下面的设计就有两个实现子类。
好处:同一个接口Target,可以有不同的实现。例如,多种加密算法的实现。
第四章 桥接模式(结构型模式)
一、桥接模式介绍
01 引例:军队机构设置
将抽象部分与实现部分分开的军队机构设置
02 举例:咖啡销售机软件
咖啡售卖机软件设计
flat 设计(version 1.0)
缺点:这个设计是扁平的,有一些重复
增加新的层次(version 2.0)
重新设计:更好,但有三层。添加一个新的咖啡需要在两个地方添加新的子类
例如:增加卡布奇诺
可扩展性不好
问题:添加新的咖啡需要添加新的子类在两个地方
将抽象部分与实现部分分开(version 3.0)
解决方案:将类层次结构划分为两个类层次结构
在Med或者Super类中,调用Latte或者Mocha(version 4.0)
可扩展性增强了
在两个维度上可以自由地添加类,而不会影响到其它的类
二、桥接模式理论
01 图示
02 桥模式的组件
- Abstraction(抽象)
- 定义抽象的接口
- 维护对实现者类型的对象的引用
- RefinedAbstraction
- 扩展抽象定义的接口
- Implementor
- 定义实现类的接口。
- 这个接口不必完全对应于抽象的接口;事实上,这两个接口可以有很大的不同
- 抽象部分与实现部分的责任
Typically- 实现接口仅提供原始操作(例如,写入数据库,写入数据文件等等)
- 抽象接口定义基于原始操作的高层操作(高层业务逻辑)
- ConcreteImplementors
- ImplementorA and
ImplementorB - 实现 Implementor 接口和定义其具体实现
- ImplementorA and
03 桥接模式的优点
- 将接口与实现解耦
- 实现部分动态配置
- 取消编译时对实现部分的依赖
- 改变实现类不需要重新编译抽象类与客户类
- 改善了可扩展性
- 可以独立地扩展抽象和实现者层次结构
- 对用户隐藏实现细节
- 可以使客户机不受实现细节的影响,比如实现对象的共享以及相应的引用计数机制(如果有的话)。
04 何时使用桥接模式?
- 适用性
- 使用桥模式,当你想要避免永久绑定之间的抽象及其实现。
- 例如,当必须在运行时选择或切换实现时
- 桥接模式的典型交互
三、桥接模式设计实例
01 举例:冰激凌销售机
(1)实例描述
考虑一个冰激凌销售机的例子,该机器销售系统包含两个维度:杯子大小与冰激凌品牌。
杯子的体积上暂时分为 中杯(Medium cup)、大杯(Super Cup);
在冰激淋的品种上,分为哈根达斯(Haagen-Dazs)、雀巢(Nestle)、冰雪皇后(Dairy Queen)
(2)实例类图
(3)实例交互
(4)程序用户图形界面
(5)源代码
class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals ( FINDPRICE )) {
String size = getCup(); // 获得用户输入:杯子大小
String kind = getBrand(); // 获得用户输入:冰激凌品牌
//Create a Brand object
if( kind.compareTo(HAAGENDAZS)==0 )
brand = new HaagenDazs(); // 创建冰激凌品牌对象
if( kind.compareTo(NESTLE)==0 )
brand = new Nestle();
if( kind.compareTo(DAIRYQUEEN)==0 )
brand = new DairyQueen();
if(size.compareTo(SUPERCUP)==0)
cup = new Super(brand); // 创建Cup对象
if(size.compareTo(MEDIUMCUP)==0)
cup = new Medium(brand);
float price = cup.getPrice();
}
} }
// 抽象接口类
public interface Cup {
public abstract float getPrice();
}
// 抽象部分子类
public class Medium implements Cup{
private Brand b;
public Medium (Brand b){
this.b = b;
}
public float getPrice(){
float iceCreamPrice = b.price();
return iceCreamPrice;
}
}
public class Super implements TeaCup{
private Brand b;
public Super (Brand b){
this.b = b;
}
public float getPrice(){
float iceCreamPrice = 1.5f * b.price();
return iceCreamPrice;
}
}
//实现部分的抽象接口。
public interface Brand {
public abstract float price();
}
//实现部分的实现类。
public class HaagenDazs implements Brand{
private final float PRICE = 6.0f;
public float price(){
return PRICE;
}
}
public class Nestle implements Brand{
private final float PRICE = 5.5f;
public float price(){
return PRICE;
}
}
public class DairyQueen implements Brand{
private final float PRICE = 5.0f;
public float price(){
return PRICE;
}
}
02 举例 特工信息系统
(1)实例描述
下面的设计代表了一个代理信息系统。代理信息被加密并保存到文本文件或数据库中。
有两种方法可以加密代理的名称和代码号,它们由类 EncryptedInfo1和 EncryptedInfo2表示。
(2)实例类图
(3)实例分析
EncryptedInfo1的加密算法:折叠算法
1.将26个英文字母按照顺序排列,加密的时候,以折叠的方式进行:aßàz, bßày, ...,mßàn
2.大写字母也是如此。大写字母加密为大写字母。
3.数字加密也以此方式进行
EncryptedInfo2的加密算法:分组互换算法
1.将26个英文字母按照顺序排列,按照如下方式两两分成一组,本组内部 加密的时候互换:a<->b, c<->d,…, w<->x, y<->z
2.大写字母的加密方式也是如此,大写字母加密为大写字母。
3.数字加密:0<->1, 2<->3,4<->5, 6<->7, 8<->9
可以在两个维度上增加新的类而不影响其它的类
第五章 策略模式(行为模式)
一、关于设计模式的调用
- 关于设计模式的使用
二、策略模式的引入
举例:排序程序设计
通过分别使用下列排序算法,设计一个程序对整数进行排序
冒泡排序、堆排序、插入排序、快速排序
设计1:使用一个名为sort的类来实现所有排序算法
类图
评价:最愚蠢的设计
问题:
(1)增加新算法,需要重新编译整个类
(2)修改算法,需要重新编译整个类
(3)可复用性差
设计2:将类划分为两个类,一个Client类,一个Sorting类(包含所有所需的排序算法)
类图
评论:比较正常思维人的设计
优点:可复用性
缺点:
(1)扩展:增加新算法,需要重新编译整个类
(2)修改:修改算法,需要重新编译整个类
(3)自顶向下的依赖
设计3:进一步拆分类:将类拆分成四个类
类图
评论:依赖倒转——21世纪新思维
优点:
(1)当添加新算法时,现有的排序类的层次结构不需要更改和重新编译
(2)当算法被修改时,排序层次结构中的其余类不需要被修改和重新编译
(3)clent 类和实现类都依赖于抽象接口
设计3实际上使用了策略模式的思想
三、策略模式理论
01 策略模式图示
02 策略模式参与
- Strategy
- 声明一个对所有支持的算法通用的接口。
- Context使用这个接口来调用 ConcreteStrategy 定义的算法。
- ConcreteStrategy
- 使用 Strategy 接口实现算法。
- Context
- 使用 ConcreteStrategy 对象配置。
- 维护对 Strategy 对象的引用。
- 可以定义一个让 Strategy 访问其数据的接口
03 策略模式分析
什么时候使用策略模式?
(1)当要处理一些仅仅行为不同的相关的类的时候
当许多相关类仅在行为上不同时。策略提供了一种方法来配置具有许多行为之一的类。
(2)当要隐藏复杂算法的实现细节的时候
当算法使用客户不应该知道的数据时。使用Strategy模式可以避免暴露复杂的、特定于算法的数据结构。
(3)当你要取消许多条件语句的时候
当一个类定义了许多行为,并且这些行为在其操作中表现为多个条件语句时。
将相关的条件分支移到它们自己的 Strategy 类中,而不是许多条件。
Context 的实现方式
(1)将数据传递给Strategy
当某算法被调用时,Context类将Strategy类所需要的所有数据一次性地传递完毕。
(2)将自身作为参数传递给策略函数
Context将自己作为一个对象传递给策略类,然后策略类再利用该对象反过来调用Context类,以便获得某些数据与功能。
策略模式的工作机制
(1)客户端通常创建一个 ConcreteStrategy 对象并将其传递给Context
(2)Context将客户机的请求转发给它的 strategy
(3)客户只与Context交互(目的)
(4)图示
04 策略模式评价
策略模式优点
(1)定义重用的相关算法
① 相关算法族:strategy类的层次结构定义了Context可重用的一系列算法或行为。
② 继承可以帮助分解算法的常见功能。
(2)容易扩展:
封装算法在不同的策略类允许您更改算法独立于它的Context(独立于Context),使其更容易
理解、切换(切换到更有效的算法)、扩展。
当更改现有算法实现或向组中添加新算法时,现有策略类层次结构和Context都不受影响。
策略模式缺点
Client 必须知道不同的 Strategy——客户端必须了解策略的不同之处,然后才能选择合适的策略
四、策略模式设计实例
01 举例:排序问题的新设计
(1)实例描述
排序问题——利用策略模式的新设计
设计一个程序使用几个排序算法来排序整数数组。该程序包括:
——GUI(Graphical User Interface 图形用户界面):用于用户的输入和输出
——排序算法封装类:类封装排序算法
——计算每个算法的执行时间:获取每个排序算法的执行时间。结果将显示在GUI上
(2)实例类图
应用策略模式的带有 Context 类的整数序列排序程序设计类图
(3)实例交互
(4)源代码
class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals(SORT)) {
if (type.equals(BUBBLE))
sa = new BubbleSort(); //(1)创建策略子类对象sa
if (type.equals(HEAP))
sa = new HeapSort(); //(1)同上
if (type.equals(INSERT))
sa = new InsertSort(); //(1)同上
if (type.equals(QUICK))
sa = new QuickSort(); //(1)同时
context = new Context(sa); //(2)创建Context的对象时,将sa作为参数传递给Context
intArray = context.sortIntArray(intArray);
//(3)使用上下文对象调用sortIntArray(intArray)方法
long eTime = context.getExeTime();
//(3)使用上下文对象调用getExeTime()方法
}}}
class Context {
SortAlgorithm alg;
private long startTime;
private long endTime;
public Context(SortAlgorithm alg) {
this.alg = alg; //(1)策略子类对象alg由构造方法传入
}
public int[] sortIntArray(int[] a) {
return this.alg.sort(a, this); //(2)在sortIntArray方法中,直接使用alg调用策略类的sort方法
//(3)同时,将Context对象传递给策略类
}
public void startExecution(){
startTime = System.currentTimeMillis();
//(4)策略类利用该Context对象调用计算程序执行时间的方法startExecution()
}
public void endExecution(){
endTime = System.currentTimeMillis();
//(4)策略类利用该Context对象调用计算程序执行时间的方法endExecution()
}
public long getExeTime(){
long exeTime=0;
exeTime = endTime-startTime;
return exeTime;
}
}
public interface SortAlgorithm {
int[] sort(int[] nums, Context ct);
}
public class BubbleSort implements
SortAlgorithm {
public int[] sort(int[] intArray, Context ct){
ct.startExecution();
// 排序代码
ct.endExecution();
return intArray;
}
}
// 其余算法的实现与此算法类似,省略。
02 举例:不同图表显示数据
(1)实例描述
实例描述
在政府或商业网站上,许多数据需要用图表解释,用户可选择不同的图表以便方便地显示一些数据。
例如显示全国各个行业的外贸出口比例等等。同一组数据,可以有不同的图表显示:
条形图(Bar)、折线图(Line)、饼图(Pie)、XY散点图(XY (scatter))、面积图(area)等等图形。
为什么要使用策略模式进行设计?
假设在用户在输入一组数据以后,程序将为你根据需要画出你所需要的图表类型。
各种图表,虽表现形式不同,但都显示同一组数据,功能是相同的。可以使用策略模式实现各种图表的显示。
将该根据用户输入的一组数据,利用各种不同的图标显示。为简单起见,我们在本例子中,只使用条形图(Bar) 和 饼图(Pie)。
(2)实例类图
使用策略模式设计的绘图软件-省略Context类的情况
使用策略模式设计的绘图软件-交互情况
(3)实例图示
用户图形界面-当用户选取了Barchart的情况
用户图形界面-当用户选取了Piechart的情况
(4)实例分析
为什么在本设计中省略了Context类?
两个类BarChartGraph和PieChartGraph中,都有一个paintComponent(Graphics g)方法。
在该方法中,调用其超类JPanel的paintComponent(Graphics g)方法。
在创建BarChartGraph或PieChartGraph的对象时,paintComponent(Graphics g)方法将自动运行。
即,BarChartGraph或PieChartGraph的对象代表一个JPanel,且相应的图形,例如Bar chart,已被绘制在该JPanel上。
为了将图形显示在GUI上,只要创建BarChartGraph或PieChartGraph对象并将其贴在GUI即可。
故省略了context类。
第六章 状态模式(行为模式)
一、状态相关系统实例
猴子案例
猴子真是一种喜怒无常的动物。假设猴子有三种状态/情绪(state/mood):
- Happy:dance;
- Mad:make noises;
- Angry:scream;
- 使用一个单独的类,封装一只Monkey的所有行为(将所有的代码都封装在一个类里面)
- 设计缺点
- 方法behave()中有很多条件语句
- 添加新的与状态相关的行为并不容易
- 改善方案
- 构建一个要封装的状态类层次结构,每个状态分为一个单独的类
(通过封装每个状态进行设计进入一个单独的类)
- 构建一个要封装的状态类层次结构,每个状态分为一个单独的类
二、状态设计模式
01 状态设计模式介绍
-
Context
- 定义客户程序需要的接口
- 保持当前状态子类对象
- 可以包含部分业务逻辑
-
状态
- 定义一个接口,用于封装与上下文的特定状态相关联的行为
-
状态子类
- 每个子类实现与上下文状态相关联的行为
02 何时使用状态模式
-
当对象的行为依赖于状态,而该对象必须根据其状态(在运行时)改变其行为
-
当操作带有大量状态相关的、多部分的条件语句
- 将每个条件分支封装在一个分离的类里面
- 使得软件工程师可以将状态当作对象对待,从而可以独立改变一种状态的代码
03 协作关系
- Context代表状态相关的请求
- Context可将自己作为一个对象通过参数传递给状态对象,从而可让状态对象访问Context类的方法
- 客户对象先创建一个具体的状态子类的对象s,然后,在创建Context对象时,通过参数将s传递给Context对象。
此后,客户程序就不必与该状态对象直接交互。 - 状态转换: Context类或具体的状态子类负责
- 状态设计模式的典型协作
04 状态模式与状态转换
-
关于状态的转换的建议
- 客户类Client不负责状态转换
- 客户类只创建一个“种子对象”,然后,传递给Context对象;
- 再由Context对象根据状态变化的情况,负责创建所有的其它的对象
- 或由State层次类决定状态的转换
-
状态模式的优点
- 容易添加一个新的状态类
- 使状态转换显式
三、状态模式设计实例
01 举例:交通信号控制软件
(1)实例描述
信号灯有红,黄,绿三个状态。使用状态模式设计交通信号灯控制软件。
设计一个LightState抽象类,和3个具体的状态子类:Red、Yellow、Green
状态超类或者状态子类的任务
拍照,统计车辆个数,记录违章车辆
负责改变状态,红->绿->黄->红
Context对象提供交通灯所需要的颜色。
(2)实例图示
交通灯控制软件状态图
(3)实例类图
交通信号灯控制软件-设计1
(4)实例分析
-
关于主用户界面
-
Extends JFrame
-
自动更新图形界面:
利用JFrame类的 paint() 方法,该方法在TrafficLightGUI生成的时候,将会自动被调用。
在 paint() 方法中,使用了 repaint() 方法,该方法将自动调用将用 paint() 。
-
停顿效果:使用了Thread类的 wait() 方法。产生停顿效果,从而模拟了交通信号灯。
-
-
交通灯控制软件图形界面
(5)实例交互
本程序的交互情况
【在客户类中】客户类首先创建Red类的对象,在创建Context类的对象的时候,将Red类的对象以参数的形式传给Context类的对象cxt。
rs = new Red();
cxt = new Context(rs);
【在客户类中】在状态类中还需要保持Context类的对象,因此需要调用
rs.setupContext(cxt);
将刚刚创建的Context对象cxt传递给状态类
【在客户类中】还需要给Context类的状态变量state赋予初始值
cxt.setState(TrafficLight.RED);
【在客户类中循环调用】在方法runTrafficLights()中,使用语句
color = cxt.getColor();
cxt.doAction();
反复调用Context类的方法,以便产生循环显示灯的颜色的效果
在客户类中循环调用——进一步解释
【在Context类中】每当调用cxt.doAction()的时候,相应地状态子类的doAction()将会被调用,changeState()也将会被调用
【在状态类中】 changeState()负责反复更新Context类中的state变量
【在Context类中】 Context类的setupStateObj()根据新的状态,决定使用某个状态子类对象light,light.performTask()
(6)实例特点
- 设计的特点
- 在Context类中保持String类型的状态变量state
在TrafficLight类中保持Context类的对象cxt
在Context类中,调用状态类的方法 —— setupContext(Context)
将Context对象传入到TrafficLight类中
在状态类中,调用context类的setState(state)方法,对Context类的state变量进行更新
在Context类中,首先统一创建三个状态子类的对象。然后,在运行时,根据state变量的情况,决定使用哪个状态子类对象
- 在Context类中保持String类型的状态变量state
- 设计的优点
- 在修改状态子类的代码的时候,不需要修改Context类的代码。
在增加新的状态子类的情况下,需少许修改状态类的changeState()方法与Context类中的相应代码。
在对状态层次类添加一个新状态子类或修改状态子类时,都不需要修改客户类TrafficLightGUI。
- 在修改状态子类的代码的时候,不需要修改Context类的代码。
02 举例:银行账户业务问题
(1)实例描述
考虑某国家某银行的存款账户。一个账户在任何时候可能有的状态如下。
- 无手续费状态 (No transaction fee state)。
- 有手续费状态(Transaction fee state)
- 透支状态(Overdrawn state)
(2)实例图示
限制:在以上任何状态下,都不允许使 balance 低于超过透支上限 (-1000) 的交易发生。
(3)实例类图
(4)实例分析
存入2500美元的顺序图
BankContext 类 (Context类提供较高层业务逻辑)
- 保持数据
- balance、account number、State object, and、the transaction limits
- 给客户类提供基本的存款与取款方法
- deposit(double amt) and withdraw(double amt)
在其中,将调用与状态子类相同的方法。
- deposit(double amt) and withdraw(double amt)
State类
- State层次类提供较低层业务逻辑
- 负责转换状态
负责将当前具体状态传递给 BankContext 类
State子类
- 负责存款、取款
负责更新BankContext类中的balance
用户图形界面
(5)实例优点
- 可扩展性超好
- 因为在客户类与Context类中,都不包含关于与状态有关的条件语句,因此
- 当要修改某个状态子类的时候,不需要修改客户类与Context类;
- 当要添加一个新的状态子类的时候,不需要修改客户类与Context类,只须少许修改状态子类的changeState方法
四、状态设计模式的进一步讨论
State pattern | Strategy pattern |
---|---|
状态相关的行为 | 不是状态相关的行为 |
每个State对象中包含的行为特定于关联对象的给定状态。 | 每个Strategy对象中包含的行为是提供给定功能的不同算法。 |
状态对象可以负责更新配置给Context类的状态对象。 | 由客户类创建策略子类对象,并且传递给Context对象。 |
给定的State对象本身可以将Context放入一个新的状态。这会创建一个新的State对象作为Context的当前State对象,从而改变Context对象的行为。 | 使用Context的客户端应用程序需要显式地将策略分配给Context。 策略对象不能导致Context使用不同的策略对象配置。 |
状态的选择(转换)依赖于Context或者State对象 | 由应用类选择策略子类对象 |
State对象的选择依赖于Context对象的状态。 | 策略对象的选择是基于应用程序的需要。不在Context对象的状态上。 |
有状态转换:可以由Context类或者State层次类进行状态转换 | 没有状态转换 |
可以将一些业务逻辑包含在Context类中(包括状态转换) | 可以将一些业务逻辑包含在Context类中(没有包括状态转换) |
第七章 访问者模式(行为模式)
一、访问者模式介绍
01 举例:Animal class 问题
公司类库中已经存在生物教学项目团队写的Animal层次类(version 1.0)
网络游戏开发团队修改Animal层次类-造成了接口污染(version 2.0)
网络游戏开发团队决定合理地利用Animal层次类(version 3.0)
02 举例:税收问题
在美国,有很多种税。税可以通过使用如下的类层次结构来表示。
对于每个类,税率和计算税收的算法都是不同的(version 1.0)
本设计的缺点:将所有这些操作分布到不同的节点类会导致系统难以实现(理解、维护、改变)
例如,在每个类中添加一个方法changeTaxRate()
改善设计:将具体的计算税收的方法从层次类中分离出来,而在层次类中保留数据维护方法(version 2.0)
一个可能的解决方案是:分离类的主要功能,即让 “Tax” 类独立于应用于它们的操作
进一步改善设计(version 3.0)
税收问题相关引申
- 为什么要引入访问者方法与接受方法
- 意图
- 利用 accept() 与 visit() 方法,在Tax层次类与TaxVisitor中都精心地设计接口
- 意图
- 怎么设计接口?
- 利用accept方法建立两个类之间的接口。
- 两个对象是怎样被链接在一起的?
二、访问者模式类图
访问者模式类图——无聚合结构的情况
访问者模式类图
访问者模式
- 访问者模式组件的解释
- 访问者类为每个元素子类准备了一个访问者方法。
Visitor类为对象结构中的每个Element类声明一个Visit操作。
操作的名称和签名标识向访问者发送Visit请求的类。例如,visitHardDisk方法 - 访问者类决定要访问哪个类。
让访问者确定被访问元素的具体类。然后访问者可以通过元素的特定接口直接访问元素
- 访问者类为每个元素子类准备了一个访问者方法。
- 访问者模式构件的解释(Visitor1,Visitor2)
- 实现Visitor类的接口中所声明的所有操作
- 提供算法环境、存储局部状态、积累遍历结构体的结果
- Element
- 定义accept()方法
- ElementA,ElementB:实现accept()方法,还可以实现一些其它方法
三、访问者模式设计实例
01 举例:计算机部件销售软件系统
(1)实例描述
-
需求
- 计算机部件销售软件系统的例子。
假设要写一个为计算机商店使用的计算机部件销售软件。
该软件要求用户使用GUI选择要购买的部件,提交,然后程序分别列出各个部件的名称与单价,以及总价格。
- 计算机部件销售软件系统的例子。
-
设计
- 考虑到计算机部件的种类相对固定,所以我们采取使用访问者模式进行设计。
(2)实例类图
(3)实例分析
-
设计要点
- 每个计算机部件类都有一个getPrice()方法,与一个accept()方法。
PriceVisitor通过accept()方法,调用getPrice()方法,达到计算价格的目的。
类似地,PartsInfoVisitor类负责调用getDescription方法,实现获取部件的具体描述。
- 每个计算机部件类都有一个getPrice()方法,与一个accept()方法。
-
在本设计中,使用了CompositeStructure类。ComputerPartsGUI类根据用户请求提交的计算机部件名称,
- 创建各个相应的部件对象,然后,
将这些对象添加到复合对象CompositeStructure中。
- 创建各个相应的部件对象,然后,
-
CompositeStructure对象负责接受访问者PriceVisitor对象,对该聚合对象所包含的各个对象访问。
-
关于CompositeStructure
- 在CompositeStructure类中,封装了长度可以弹性增长的数据结构ArrayList。
在CompositeStructure对象中,可以添加许多等待访问的对象,而不必关心数组越界问题。
类CompositeStructure负责“批量”接受访问者访问所需要被访问的对象。
- 在CompositeStructure类中,封装了长度可以弹性增长的数据结构ArrayList。
-
访问者模式设计的程序的典型交互
-
交互分析
- 在以上客户程序的代码实现的中,首先创建了CompositeStructure类的对象,
- 然后调用了该类的方法accept(PriceVisitor pv) 与accept(InventoryVisitor iv)。
- 在以上的accept方法中,采用了一个循环语句,但是没有条件语句。也就是说,CompositeStructure类的对象负责将等待访问的Part对象保存,但是并不关心确切地哪个对象被保存了。
- 当需要接受访问者的时候,使用一个循环语句,遍历所有的待接受访问的对象,使得每个对象都接受访问。
(4)源代码
// 在下面用户图形界面源代码中,省略了大量的语句
public class ComputerPartsGUI extends JFrame implements ItemListener{
private void createPartObjAndVisitParts(ActionEvent e){
ComputerParts part = null;
PriceVisitor pv = new PriceVisitor();
PartsInfoVisitor iv = new PartsInfoVisitor();
CompositeStructure comStruct = new CompositeStructure();
//循环语句
if(part != null){
comStruct.attach(part);
comStruct.accept(pv);
comStruct.accept(iv);
}
}
public class CompositeStructure{
private ArrayList<ComputerParts> parts;
public CompositeStructure(){
parts = new ArrayList<ComputerParts>();
}
public void attach(ComputerParts equip){
if(equip != null)
parts.add(equip);
}
public void detach(ComputerParts equip){
if(equip != null)
parts.remove(equip);
}
public void accept(Visitor v){
int len =parts.size();
for (int i=0; i < len; i++){
ComputerParts part = parts.get(i);
part.accept(v);
}
}
}
public abstract interface ComputerPart {
public abstract void accept(Visitor vis);
public abstract String getName();
public abstract double getPrice();
public abstract String getDescription();
}
public class Microprocessor implements ComputerPart{
public static final String NAME = "Microprocessor";
private final double PRICE = 80.00;
public static final String FEATURES = "Microprocessor. Intel BJ786";
public String getName(){
return NAME;
}
public double getPrice(){
return PRICE;
}
public String getDescription(){
return FEATURES;
}
public void accept(Visitor v){
System.out.println("Microprocessor has been visited.");
v.visitMicroprocessor(this);
}
}
public abstract interface Visitor{
public abstract void visitComputerCase(ComputerCase e);
public abstract void visitPowerSupply(PowerSupply e);
public abstract void visitMotherboard(Motherboard e);
public abstract void visitMicroprocessor(Microprocessor e);
public abstract void visitMemory(Memory e);
public abstract void visitDriveController(DriveController e);
public abstract void visitHardDiskDrive(HardDiskDrive e);
public abstract void visitCDDrive (CDDrive e);
public abstract void visitDVDDevice (DVDDevice e);
public abstract void visitMonitor (Monitor e);
public abstract void visitKeyboard (Keyboard e);
public abstract void visitMouse (Mouse e);
public abstract void visitFan (Fan e);
public abstract void visitVideoCard(VideoCard e);
}
//本类省略了大量代码
public class PriceVisitor implements Visitor{
private double total =0 ;
public double price = 0;
ArrayList<Double> partsPrices;
public PriceVisitor() {
partsPrices = new ArrayList<Double>();
}
public void visitComputerCase(ComputerCase e) {
price = e.getPrice();
partsPrices.add(new Double(price));
total += price;
}
public ArrayList<Double> getPartsPrices() {
return partsPrices;
}
public double getPriceTotal(){
return total;
}
}
02 举例:几何形状计算
(1)实例描述
计算各种几何形状的几何数据
设计:考虑到
对象结构包含很多个子类;
这些类有不同的接口;
你需要针对不同的类进行不同的操作。
所以我们采取使用访问者模式进行设计。
(2)实例类图
四、进一步的讨论
何时使用访问者模式?
(1)对象结构包含很多个子类,而这些类有不同的接口。你需要针对不同的类进行不同的操作。
(2)许多不同的且不相干的操作需要施加于对象结构,而你想要避免这些不同的操作污染这些类。
(3)对象结构类很少改变(例如税收的种类很少改变),但是你需要经常地在这些结构体上增加新的运算(税率可能经常改变)。
(4)注意,
使用Visitor模式的客户端必须创建一个ConcreteVisitor对象,然后遍历对象结构,使用Visitor访问每个元素。
当访问一个元素时,它调用与它的类相对应的Visitor操作。元素将自身作为此操作的参数提供,以便访问者在必要时访问其状态。
访问者模式的优点
(1)访问者使添加新操作(在访问者中)变得容易。
访问者可以很容易地添加依赖于复杂对象组件的操作。只需添加一个新的访问器,就可以在对象结构上定义一个新操作。
相反,如果将功能分散到许多类上,则必须更改每个类来定义一个新操作。
(2)访问者收集相关操作并分离不相关操作。
例如,将计算总价格放在一个具体的visitor类中
相关行为并没有分布在定义对象结构的类中;它是在访问者中本地化的。
不相关的行为集被划分到它们自己的访问者子类中。
访问者模式的缺点
添加新的ConcreteElement类很困难。
每个新的ConcreteElement都会在Visitor上产生一个新的抽象操作,并在每个ConcreteVisitor类中产生相应的实现
ConcreteElement->功能
讨论:访问者模式的重点
-
什么是访问者模式的重点?
- 用于使得被访问者与访问者之间建立自动访问的accept方法是重点。
该方法建立了两个层次类之间的关联。该方法使得自动访问成为可能。
- 用于使得被访问者与访问者之间建立自动访问的accept方法是重点。
-
注:类objectStructure类对于批量访问很重要。
另外使用此类,可以使得客户类包含较少的条件语句。能够有效地做到责任分离。 -
结论:要写好 accept() 方法与 objectStructure
第八章 中介者模式(行为模式)
一、中介设计模式的介绍示例
01 举例:机场指挥塔的功能
(a)指挥起飞:所有的飞机必须接到起飞的命令才能起飞
(b)指挥降落:所有的飞机必须接到降落的命令才能降落
(c)通讯:飞机和指挥塔通讯;所有的飞机不能直接通讯
通讯方式:星形
二、中介者模式理论
01 基本描述
(1)面向对象应用程序由一些互动的对象组成
通常,OO应用程序由一组对象组成,这些对象为了提供服务而相互交互。
(2)当参与对象数目较少时,对象之间可以直接交互
这种交互可以是直接的(点对点),只要直接相互引用的对象的数量非常少。
02 对象耦合
ObjectA <==> ObjectB
我们经常这样做,例如:
(1)在策略模式和状态模式里面,Context类的对象与Strategy层次类对象的耦合是双向的
(2)在状态模式里面,Context类的对象与State层次类的对象之间的耦合是双向的
03 点对点通信:增加对象数量
高耦合
随着对象数量的增加,这种类型的直接交互可能导致对象之间很复杂。
如果要增加一个新的类,则所有类的接口均需要改变
04 点对点通信的缺点
这么多物体的高耦合,
(1)导致复杂的方法调用
(2)影响应用程序的可维护性(如果你要增加一个新的类,就太麻烦了)
(3)由于更高的耦合,大大减少了重用这些对象的范围
05 重新设计:星形拓扑
为了减少对象之间的紧密耦合,可以使用星形形式重新设计中介设计模式
中介设计模式的逻辑关系图
怎样设计类图才能实现以上的逻辑图?
(1)Mediator类应该与参与者类(对象A, B, C, … ,H的类)之间有某种形式的关联,
(2)而参与者类之间不应该有关联。
06 类图描述逻辑
07 中介者相关问题
- 为什么中介者类必须拥有 register 方法?
- 中介类必须知道所有具体参与的Colleague ,因此该类需要一个register 方法
- 可以使用 register 方法让中介类保留所有参与对象
- 为什么每个 Colleague 子类都必须保持中介者类的引用吗?
- 另一方面,每个参与类必须保留中介类的引用,以便它可以调用中介类中的方法来执行某些操作。
08 中介者模式设计类图
-
该设计类图由两部分组成,一部分是中介者类,另外一部分是以上的参与者对象。
-
程序的构件说明:
- Mediator:中介者的接口
- ConcreteMediator:具体的中介者,可以有多个具体的中介者
- Colleague:参与者对象接口
- Colleague1, Colleague2:具体的参与者。可以有多个具体的参与者
09 中介者模式的用途
-
我们可以使用中介模式来减少对象之间的直接调用
-
中介模式表明抽象对象交互细节到一个单独的类,Mediator 类负责所有对象交互
- Mediator 类每个参与者类的引用
- Mediator 类提供调用参与对象的方法
10 中介者模式的典型交互
- 任何两个不同对象之间的交互都通过Mediator类路由。
- 所有对象将它们的消息发送给中介(调用中介的方法)
- 然后,中介将消息发送到适当的对象,以实现应用程序的需求(然后在中介中调用每个其他对象的方法)
- 在中介者模式中对象如何交互
- Object B 调用 Mediator对象的方法。
然后中介对象将调用 Object E、F、G 中的方法。
Object E、F、G也可以回调 Mediator 对象中的方法。
Mediator 对象可以依次调用其他对象。 - 注:Object B 可能不知道调用了哪个对象
- Object B 调用 Mediator对象的方法。
11 中介者模式的特点
中介者模式的优点
- 参与者类复用性变得更好
- 将对象间依赖关系移出单个对象可以增强对象的可重用性。
- 参与者对象之间的关系可以由中介者子类对象调节
- 通过将中介替换为具有扩展或更改功能的子类,可以更容易地更改对象间关系的行为。
- 具有两个具体的中介者的模式的例子
- 有利于参与者类的单元测试
- 对象可以更容易地进行单元测试,因为对象不需要直接引用彼此。
- 有利于参与者类的修改
- 低耦合度允许在不影响其他类的情况下修改单个类。
中介者模式的缺点
使用中介可能会降低性能
三、中介者模式的设计实例
01 实例描述
酒店-航空公司-旅游信息系统
有关协作程序的Mediator模式实现,请参见下面的类图
酒店(宾馆)、航空公司(航空公司)、旅游(旅游公司)。
02 实例类图
03 实例分析
目的:三家企业信息共享
前台接待员在酒店信息GUI中输入客户信息时,航班信息GUI和旅游信息GUI会显示相同的信息,包括客户的名字、身份证号码、国籍
这样三家公司就可以共享信息
04 实例设计
初步设计:3个对象, Hotel, Airline, TourCompany 这3个对象有以下关系。
设计缺点:各个对象之间直接交互
——调用关系复杂
——可扩展性差
解决方案:使用中介者模式,引入中介者类BusinessMediator
使用中介来减少耦合
设计1——类图
设计1——典型交互
四、中介者模式的实现细节
01 编写中介类
例如,编写中介类,并声明具有参与中介模式设计的所有对象类型的私有变量
private hotelGui: hotelGui;
private airlineGui: airlineGui;
private tourGui: tourGui
02 编写注册方法
在中介类中,编写所有注册方法来注册所有参与的对象。例如,参与的对象类型应该分别包含在注册方法的参数中。
registerHotelGUI(HotelGUI hg)
registerAirlineGUI(AirlineGUI ag)
registerTourGUI(TourGUI tg)
03 在每个注册方法内部,将从参数传递的对象赋值给相应的私有成员。
// For,example,
registerHotelGUI(HotelGUI hg){
hotelGui = hg;
}
通过这种方式,参与的对象被“拉”到中介对象中。
主类的实现
创建中介对象。在客户端类中,应该创建中介类的对象
创建参与者对象。然后,应该用刚刚创建的对象通过参数传入的方法创建所有参与类的对象。
第九章 观察者模式(行为模式)
一、观察者模式的主要例子
01 举例:设计海洋信息分析系统
设计一个海洋信息分析系统,暂时设计2个类(version 1.0)
缺点:不间断的循环调用,浪费了CPU运行能力,效率很低。
改善设计:改善调用方式。当数据有变化时,OceanData 对象通知 DataAnalizer 对象(version 2.0)
02 举例:大气监测显示
假设一个大气监测台,用于监测北京城市大气质量,然后画出大气中所包含的可吸入行颗粒、一氧化碳浓度、二氧化碳浓度随时间的变化曲线。
最初设计(version 1.0)
缺点:这种设计很难扩展
改善设计(version 2.0)
改善设计的优点:松散的耦合
- 容易修改代码
- 当需要修改Air代码的时候,AirDsp层次类不受影响
- 当需要修改AirDsp代码的时候, Air类不受影响
- 容易扩展
- 容易在AirDsp中增加新子类,而 原来已经存在的所有的类都不受影响
二、观察者模式理论
01 基本描述
观察者模式对于设计一个在Observer和Subject之间 一致的通讯模型很有用。
观察者模式对于设计一个一致的通信模型非常有用,
观察者:一组依赖的对象和
主体:他们依赖的对象
当你可以将一个程序中的对象分为主题与观察者的时候,可以使用如下的观察者模式进行设计。
02 模式类图
03 典型交互
04 相关问题
(1)一个主题可以有多个观察者。
一个实验对象可以有不止一个这样的观察者。每一个观察者都需要知道主体的状态何时发生变化。
(2)主题保持一个动态列表,记录已经注册的观察者
主题必须保持一个动态注册观察者列表。
(主题不能维护这样的观察者的静态列表,因为给定主题的观察者列表可能会动态更改)
(3)观察者模式所包含的必须的活动
① 观察者必须注册自己
任何对主体状态感兴趣的对象都需要显式地将自己注册为主体的观察者。
② Observable必须通知观察者
当主体的状态发生变化时,它会通知所有注册的观察者。
③ 观察者可以查询观察对象的状态
在收到来自主题的通知后,每个观察者都会查询主题以使其状态与主题的状态同步。
(4)通知观察者模式的两种策略
① push model (将观察者所需数据全部推给观察者,例如:
update(Object a)):主体应该发送观察者可能感兴趣的状态信息。
② pull model(主题提供接口,供观察者访问,例如:
update(Observable obs)):主体应该提供一个接口,使观察者能够查询主体所需的状态信息来更新他们的状态。
05 模式优点
- 可以动态添加观察者而不需要影响主题。
- 可以动态地添加不同的观察者,而不需要对Subject类进行任何更改。
- 当主题的状态或者逻辑发生变化的时候,观察者不受影响。
- 当主体的状态或逻辑发生变化时,观察者不会受到影响。
06 何时使用
- 一个对象的状态改变必须反映在另一个对象中,而你又不想让两个对象保持高耦合。
- 状态的变化在一个对象必须反映在另一个对象没有保持对象紧密耦合。
- 你设计的框架将来需要添加许多观察者,而你希望以最小限度地改变程序。
- 我们编写的框架需要增强在未来新观察者以最小的变化。
三、Java对观察者模式的支持
01 Java语言环境对观察者模式的支持
Java API提供了以下的(采用了既拉又推的方式)
- Observable类:在该类中,所有的三个方法都已经实现了。
- addObserver(observer:Observer)
setChanged()
notifyObservers(event: Object)
- addObserver(observer:Observer)
- Observer 接口。在Observer 接口中,方法带有两个参数。
- update(o: Observable, e: Object)
02 Java支持的观察者模式类图-既拉又推
03 Java支持的观察者模式-典型交互
04 Observable/Observer策略工作机制
- 被观察者可以将一个观察者添加到一个list. 这也说明被观察者同意被该观察者观察。
- setChanged()和notifyObservers()将通知观察者主题对象的状态已经改变了,观察者对象的update()方法将自动运行。
- 在观察者对象的update()方法中,如果需要,可以调用被观察者中的相应方法,确切了解状态变化的细节。
四、观察者模式的设计实例
01 实例描述
温度转换程序设计的例子。
我们试着用摄氏、华氏和开氏来表示温度。我们使用一个TemperatureGUI供用户输入温度
Celsius or
Fahrenheit or
Kelvin.
然后,其他3个图形界面将以摄氏和华氏两种方式显示温度。
在这种情况下,我们可以使用观察者设计模式,将TemperatureGUI封装为实现可观察接口的类,
封装CelsiusGUI, FahrenheitGUI和KelvinGUI作为3类实现接口观察者。
02 Design 1
(1)观察者必须注册
一个观察者可以通过调用observable中的register方法将自己注册到这个可观察对象中。
(2)发生事件,被观察者必须通知观察者
notifyObservers()将通知观察者物体温度gui的状态发生了变化,然后CelsiusGUI,
FahrenheitGUI和KelvinGUI对象将通过使用update(s Observable)做一些事情,
在其中,如果有必要,将依次调用Observable来检查状态的变化。
利用观察者模式设计的温度转换程序的典型交互
设计分析
设计亮点:两个接口
Observable
Observer
他们的所有方法都在设计中声明。
在TemperatureGUI中,你需要实现所有的方法:
notifyObservers()
register(obs: Observer)
unRegister(obs:Observer)
在 CelsiusGUI, FahrenheitGUI, and KelvinGUI 中,需要实现方法:
update(Observable subject, Object arg)
源代码
public class TemperatureGUI implements Observable{
ArrayList(<Observable>) observersList = new ArrayList(<Observable>);
public void register (Observer obs) { observersList.add(obs);}
public void unRegister(Observer obs){ observersList.remove(obs); }
public void notifyObservers(String temperature) {
for (int i = 0; i < observersList.size(); i++) {
Observer observer = (Observer)observersList.elementAt(i);
observer.update (this, temperature); //call update method in all the
//observers
}
}
class ButtonHandler implements ActionListener{
public void actionPerformed(ActionEvent e){
if (e.getActionCommand().equals(SUBMIT)){
String tem = boilerTem.getText(); //get temperature
setTemperature(tem); //set temperature to the private variable
notifyObservers(bTem); //notify the observers that the temperature has
//changed
} } } }}
public class CelsiusGUI extends JFrame implements Observer{
tempGui.register(this);
public void update( Observable subject, Object arg) {
String t = (String) arg; // 从参数传入的温度值
// parameter subject represents observable,object of TemperatureGUI is
// passed in from here. See how to call the update method in method
//notifyObservers in class TemperatureGUI
TemperatureGUI tg = (TemperatureGUI)subject; //从参数传入的
//被观察者对象
String option = tg.getSelectedTemExpression(); //使用此对象调用
//被观察者
TemperatureConvertor tc = new TemperatureConvertor();
float cTem = tc.getCelsiusTemperature(option, t);
tempTextArea.setText(“”+cTem); //将摄氏温度显示在屏幕上
} }
public class TestObserverObservable {
public static void main(String[] args) throws Exception {
//create an observable object
TemperatureGUI temperatureObj = new TemperatureGUI();
//Create Observer objects
CelsiusGUI cg = new CelsiusGUI();
FahrenheitGUI fg = new FahrenheitGUI();
KelvinGUI kg = new KelvinGUI();
}
}
// 注意:这里没有addObserver方法的调用,是因为在CelsiusGUI中,已经有tempGui.register(this);
通常,在客户程序中,要做以下事情:
1)create object of observable
TemperatureGUI tempObj = new TemperatureGUI();
2)create object of observer
CelsiusGUI cg = new CelsiusGUI();
FahrenheitGUI fg = new FahrenheitGUI();
KelvinGUI kg = new KelvinGUI();
3)register observers to the observable
tempObj.register(cg);
tempObj.register(cg);
tempObj.register(cg);
4) temperatureObj.notifyObservers(bTem);
03 Design 2
使用Java API 的Observer接口和Observable类。
使用Java API提供的观察者机制——使用java内置的事件处理策略。
Observable class and
Observer interface
实例类图
实例交互
利用Java提供的类与接口与观察者模式计的温度转换程序的典型交互
-
客户类
- 在TestObserverObservable中,使用 addObserver(observer: observer)
- 添加一个观察者 CelsiusGUI, FahrenheitGUI和KelvinGUI到TemperatureGUI对象
-
被观察类
- 在类TemperatureGUI中,一起使用setChanged()和notifyObservers()通知
- 观察者对象TemperatureGUI的状态已经改变
- 然后CelsiusGUI, FahrenheitGUI和KelvinGUI对象中的 update(o: Observable, e: Object)方法将被自动调用来更新温度显示。
- 在update方法中,可以反过来调用observable来检查状态的变化。
源代码
public class TemperatureGUI extends Observable{
class ButtonHandler implements ActionListener{
public void actionPerformed(ActionEvent e){
if (e.getActionCommand().equals(SUBMIT)){
String tem = boilerTem.getText(); //获得用户输入温度值
setTemperature(tem);
setChanged();
notifyObservers(bTem);
//notifyObservers的实现中,调用了观察者类的
//update(Observable subject, Object arg) 方法
//温度bTem经由参数arg传入观察者
}
}
} }}
public class CelsiusGUI extends JFrame implements Observer{
private JTextArea tempTextArea;
private Container contentPane;
//此处省略了图形界面生成代码
public void update(Observable subject, Object arg) {
String t = (String) arg; //用户输入的温度数值
//参数subject代表被观察者TemperatureGUI对象
TemperatureGUI tg = (TemperatureGUI)subject;
String option = tg.getSelectedTemExpression();
TemperatureConvertor tc=new TemperatureConvertor();
float cTem = tc.getCelsiusTemperature(option,t);
tempTextArea.setText("Celsius Temp: \n"+ cTem);
}
}
public class TestObserverObservable {
public static void main(String[] args) throws Exception {
//create an observable object
TemperatureGUI temperatureObj = new TemperatureGUI();
//Create Observer objects
CelsiusGUI cg = new CelsiusGUI();
FahrenheitGUI fg = new FahrenheitGUI();
KelvinGUI kg = new KelvinGUI();
//Add observers
temperatureObj.addObserver(cg);
temperatureObj.addObserver(fg);
temperatureObj.addObserver(kg);
}
}
在Client类的主方法中,应该包括的内容:
1)create objects of observable
TemperatureGUI temperatureObj = new TemperatureGUI();
2)create objects of observers
CelsiusGUI cg = new CelsiusGUI();
FahrenheitGUI fg = new FahrenheitGUI();
KelvinGUI kg = new KelvinGUI();
3)register observer objects to observable object
temperatureObj.addObserver(cg);
temperatureObj.addObserver(fg);
temperatureObj.addObserver(kg);
4)notify observer
setChanged();
notifyObservers(bTem);
第二部分:软件体系结构
第一章 MVC设计模式 MVC体系结构
一、MVC设计模式 / 体系结构的引入
举例:设计一个二手车拍卖软件
-
案例描述
该软件具有图形用户界面,可以显示汽车图片、汽车说明和当前拍价 a.用户首先在车的列表中选择一款车,然后点击Search按钮 b.该车的图片,描述,与当前拍价将在屏幕上显示 c.用户输入新拍价 d.新的拍价在另外一个视窗中显示
-
关于数据显示:有三种显示方法
- 被选中车的数据:[ 图片| 描述 | 当前竞拍价格 ]
-
设计1:仅仅使用一个Java类实现全部功能
- Java类(产生用户图形界面、包含所有的业务逻辑)
- 设计:包含GUI、业务逻辑、数据库访问
- 缺点:可扩展性不好、可维护性不好
-
设计2:将整个项目设计为两个组件
-
用户界面类 CarAuctionGUI
1、提供用户输入功能 2、显示查询结果
-
类AuctionFunctions
1、从一个文件夹中提取车的信息描述文件 2、相关的文件显示功能 3、从一个文件夹中提取车的图片文件 4、相关的图片显示功能 5、处理竞拍价格 与 竞拍价格显示功能 6、所有的业务逻辑方法 7、可能的数据库访问方法
-
优点
-
实现了责任分离
-
1、在用户界面程序中,除了搭建所有的图形组件外, 仅仅提供了用户输入(和简单的输出功能)的获取功能: getSelectedCar(): String getBitPrice(): String 2、将所有其它的功能都放到单独的一个类 AuctionFunctions中
-
-
缺点
-
在一个类AuctionFunctions中,集中了业务逻辑、所有的辅助功能,造成了接口污染
-
-
-
设计:MVC设计模式
-
新设计方案考虑将来(追加)可能扩展的功能:
1、要求在输入方面有可扩展性,例如键盘输入,图形界面上的按钮输入,手机、平板的触摸屏输入 2、对于汽车信息,允许有不同的显示:图片显示,文字显示,性能比较、分析图形,销售情况(柱形图,饼形图) 3、希望将所有的业务逻辑都封装到一个类中
-
解决方案:使用MVC (Model-View-Controller)设计模式进行设计
-
二、MVC设计模式理论
01 定义
-
定义:在软件工程中,模型-视图-控制器 (MVC) 是一种架构模式,它将用户和应用程序之间的交互分为三个角色:
- 模型 (业务逻辑)
- 视图 (用户界面)
- 控制器 (用户输入)
-
组件:模型、视图、控制器
-
连接器:显式调用、隐式调用或其他机制(例如HTTP协议)
-
MVC模式架构图
02 Model、View与Controller
-
Model(模型)的责任
- 负责从数据库中提取数据;将数据存储到data store
- 负责业务逻辑实现
- 负责数据验证,然后将数据存入数据库
-
View(视图)的责任
- 捕捉用户输入
- 向controller分发处理请求
- 显示输出 (遵照控制器指示)
-
Controller(控制器)的责任
- 接收来自客户的请求
- 调用model业务逻辑方法
- 调用View显示执行结果
-
三者之间的关系
- 一个model可能有多个View
- MVC模式架构图-交互情况
03 两个分离
- MVC架构的两个主要的分离
- 将表示从模型中分离出来
- [ 汽车拍卖系统 ] <==> 将整个程序设计为一个单独的类的情况
- [ GUI with controller ] ——> [ Business Model ] <==> 用户图形界面和控制器在一起的情况
- 将控制器从View中分离出来
- [ GUI with controller ] ——> [ Business Model ] <==> 用户图形界面和控制器在一起的情况
- Model <---> View <---> Controller ---> Model <==> 使用MVC架构进行旧车拍卖系统程序设计的情况
- 将表示从模型中分离出来
04 数据更新
数据更新:改变-传播机制(change-propagation)
如果用户通过一个 view 的 controller 改变了 model ,所有的view必须反映出该改变。
当数据发生变化的时候,model 通知所有的 view ,告诉他们数据已经改变了。
Views 可以遍历 (访问) model 中的数据,以便发现到底是什么改变了。然后更新显示数据。
注意,改变-传播机制保证了用户界面和模型的一致性。可由观察者模式实现。
05 MVC的优点
- 容易增加或者改变视图
- 业务逻辑被封装在Model中,所以在新增加一个视图的时候,不必要改动模型,而是因为业务逻辑都是一样的,所以只需要新增加一个视图类即可。
- 容易独立地更新每个独立的软件模块
- 一个应用被分离为三个软件模块
- 我们可以独立地改变其中的一个模块,而不影响其它两个模块
- 例如,一个应用的业务流程或者业务规则的改变只需改动MVC的Model模块
- 代码易开发易维护
- 业务逻辑更易测试
06 MVC Frameworks
MVC架构在以下的架构中均有所体现
J2EE:
Struts
Spring MVC
PHP
CakePHP
Strusts4php
C#.NET
Girders
三、MVC设计模式实例
01 网上二手车拍卖系统的设计方案
-
采用MVC模式-使用观察者机制的情况,用户输入界面和两个显示视图分别独立显示
-
将事务逻辑部分和用户界面部分分开,采用MVC模式。设计如下:
CarAuctionGUI,提供用户输入界面,代表用户; CarModel,Model部分,封装事务逻辑功能 CarBitView,view部分,显示用户给出的拍卖价格 CarGUIView,View部分,显示汽车图片 Controller,controller部分,处理用户输入,调用model和view
02 二手车拍卖系统类图
- 二手车拍卖系统的类图设计
- 二手车拍卖系统的典型交互
- 01
- 02
- 01
03 设计方案工作原理
- 用户输入
- 用户通过使用CarAuctionGUI中的Cars列表选择待卖的车,点击Search,以便获取车的图片;然后输入拍价,点击Bit,给出价格。
- 捕捉用户输入
- 每次点击按钮产生的事件,都被Controller对象捕捉,然后调用actionPerformed()方法。
- 更新模型数据
- 在actionPerformed()方法中,将根据事件的类型,分别使用setSelectedCar 或者setBitPrice 方法更新CarModel的数据。
- Model通知View
- 由于CarGUIView、CarBitView与CarModel之间存在观察者/被观察者的关系,所以当CarModel的状态改变了的时候,CarGUIView和CarBitView的update()方法将被自动调用。
- 更新View
- 在update()方法中,调用CarModel的getSelectedCar 或者getBitPrice 方法得到最新的数据,然后根据数据,更新视图。
04 设计要点
-
CarModel实现了interface Observable,实现了方法notifyObservers()和register(Observer obs)。
-
CarGUIInfoView和CarBitInfoView实现了interface Observer, 实现了方法update()。
-
每个CarGUIInfoView和CarBitInfoView都将自己注册为CarModel的观察者。
每当CarModel的状态改变的时候,其方法notifyObservers将改变告诉给它所有的观察者,观察者的update()可以自动查询CarModel,了解到底是什么状态改变了,从而作出响应的反应。
第二章 层次体系结构
一、层次体系结构的概念
01 定义
层次(分层)系统是按层次组织的。每层都为其上面的层提供服务,并充当下面层的客户。
02 图示
03 拓扑限制
- 交互仅仅在相邻层进行
- 不允许隔层调用
- 不允许有相反方向的调用
04 典型应用领域
- 典型应用领域
- 分层通信协议(如OSI模型,TCP/IP模型)
- 数据库系统
- 操作系统
- 应用程序(如 .net 和 JavaEE 开发)
- 举例:7层层次通讯协议
- 每层都提供在一定层次上的通讯模块
- 低层定义低层交互
- 最低层通常由硬件连接
05 层次架构的优点
- 分层架构支持基于逐层增加的抽象级别的设计
- 这允许实现者将复杂问题划分为一系列的逐渐增加的抽象级别的步骤。
- 支持更新
- 每个层最多与下面和上面的层交互,
- 对一层功能的更改最多会影响其他两层。
- 支持复用
- 同一层可以有不同的实现,只要它们支持到相邻层的相同接口。
- 通过标准层接口,它可以由不同的软件开发团队构建。
06 层次架构的缺点
- 并不是所有的系统都能很容易地以分层的方式构建。
- 即使一个系统在逻辑上可以分层结构,性能方面的考虑也可能需要高层函数与其更低级的实现函数直接的紧密耦合。可能很难找到正确的抽象层次。
二、三层软件体系结构
01 三层客户端-服务器(3-tired Client/server)架构
-
三层架构是近年来经常使用的软件体系结构。
-
该体系结构可以分为通用的3层架构与运行在互联网上的三层架构软件。
- 运行在互联网上的三层架构被称为三层客户端-服务器(3-tired Client/server)架构
- 通用三层架构。本节要讲述的是通用的三层架构。
-
传统的三层的层次体系结构-与Internet无关
02 表示层 (Presentation layer)
- 表示层通常包括用户图形界面,用于用户输入、用户请求与显示用户请求的返回结果等。
- 表示层调用应用层组件的过程,函数或者方法。但是应用层从来不会调用表示层的功能。
03 应用层 (Application layer)
- 或者称为业务逻辑 (Business Logic) 层。
- 主要包括应用的业务逻辑,实现了应用的商业功能。
- 该层的组件封装了应用的核心数据与功能。
- 在该层中,如果要访问数据库或者要将程序运行中产生的数据存储到数据库,
必须调用永久数据存储层的相应的数据库访问方法,而不能直接访问数据库。
04 永久数据存储层 (Permanent Data Storage layer)
- 该层包含数据库的访问与将永久数据存储到数据库中的功能。
- 在Java实现中,该层包含了利用JDBC写的数据库访问代码。
- 该层不能调用应用层与显示层,而只能将执行应用层的请求对数据库的访问结果返回给应用层。
而应用层有可能将该数据返回给显示层。
三、三层架构与MVC架构的比较
-
在形式上,三层架构类似于MVC 架构,都是由三部分软件模块组成的,但是实际上它们是不同的。
-
两种架构相似之处如下:
- 都是由三部分软件模块组成的
- 三层架构的显示层与MVC架构的View类似
- 三层架构的应用层与MVC架构的Model类似
-
3层层次架构与MVC架构
-
三层体系结构与MVC体系结构的区别
- 区别1:各个模块之间的调用关系不同
- 三层架构的一个根本原则是显示层不能直接越过应用层直接调用永久数据存储层的代码。首先显示层必须调用应用层,然后再由应用层的相关的方法转而调用永久数据存储层。不允许有相反方向的调用。因此,三层架构的交互是线性的;
MVC架构的程序组件之间的交互是三角形的:View对象发送更新给Controller对象,Controller对象更新Model对象,并且View对象直接地从Model对象得到更新。
- 三层架构的一个根本原则是显示层不能直接越过应用层直接调用永久数据存储层的代码。首先显示层必须调用应用层,然后再由应用层的相关的方法转而调用永久数据存储层。不允许有相反方向的调用。因此,三层架构的交互是线性的;
- 区别2:对数据库的访问方式不同。
- 三层架构指定一个永久数据访问层,所有对数据库的访问均有此层承担;
- MVC架构没有指定专门的数据库访问模块,一般的情况下是由Model直接访问数据库,但是也没有排除Controller中直接访问数据库的可能。
- 3层层次架构与MVC架构访问数据库的情况
- 区别3:关于控制模块(Controller)
- MVC架构中存在专门的Controller模块;
- 而层次架构中通常没有这样专门的模块。
- 事实上,很多设计者在层次架构中的应用层里面单独地指定一个类似的控制模块。
- 很多设计者在层次架构中的应用层里面单独地设计一个Controller模块,其主要责任是接收从显示层输入的用户请求,然后根据请求的类型,决定调用应用层的其它组件。
- 区别1:各个模块之间的调用关系不同
-
关于观察者模式在三层架构与MVC架构中的使用
- 在三层架构中,可以在应用层与表示层之间,使用观察者模式:将应用层作为被观察者,将表示层作为观察者。
- 其目的是一旦应用层的状态改变的时候,及时通知表示层,以便及时刷新用户图像界面。
- 在MVC架构中,可以在Model与View之间使用观察者模式: Model作为被观察者, View作为观察者。
- 一旦Model的状态改变的时候,及时通知View,以便及时刷新View。
-
使用观察者模式的3层层次架构与MVC架构
- 注意:小小的”违规“
- 如果在3层架构中,应用层与表示层之间,使用观察者模式,
则必然导致应用层对显示层的调用(notifyObservers())。这违反了层次架构的原则。 - 但是考虑到采用观察者机制更新图形界面的效率,以上的小小的“违规”也是值得的。
- 如果在3层架构中,应用层与表示层之间,使用观察者模式,
第三章 主程序-子程序系统与面向对象系统
一、程序设计进化
01 程序设计进化
COBOL:非结构化编程语言代表
C:结构化编程语言代表
C++:面向对象语言,面向对象编程
Java:100%面向对象编程语言
02 非结构化编程
- 非结构化编程基本知识
- 所有程序代码都写在一个连续的主程序中。
一行包含一条语句,带有一个数字标签,表示允许程序执行从一行跳转(使用GOTO)到另一行的行号
非结构化编程引入了:迭代、分支和GOTO
非结构化语言代表:早期BASIC、COBOL
- 所有程序代码都写在一个连续的主程序中。
- 非结构化编程的缺点
- 很难追踪程序逻辑;
很难合并其它代码;
修改代码并不容易(都是goto惹的祸);
很难测试特定部分的代码。
- 很难追踪程序逻辑;
03 结构化编程
- 结构化编程的定义
- 结构化编程是一种编程范式,旨在通过广泛使用:
子程序(subroutines):
块状结构 (block s tructures):
循环结构:for和while循环。
来提高计算机程序的清晰度、质量和开发时间。
结构化编程试图抛弃GOTO语句。
结构化编程代表语言: ALGOL68, C
- 结构化编程是一种编程范式,旨在通过广泛使用:
- 结构化程序定理:只要一种编程语言可以依三个方式组合其子程序及调整控制流程,则每个可计算函数都可以用此种编程语言来表示(实现任何算法)。三个调整控制流程的方式为:
序列结构:按照顺序执行一个子程序,然后再执行另一个子程序
选择结构:根据布尔表达式的值,执行两个子程序中的一个子程序(if, else if, else);
迭代结构(for, while):执行子程序,直到一个布尔表达式为true - 该定理强调使用子程序、块状结构和for和while循环;而不是使用简单测试后再使用跳跃语句,例如goto语句;
该定理保证了只要一种编程语言支持序列结构、选择结构、循环结构(而不必使用goto语句语句),则可以实现任何可计算函数;
该定理是结构化编程的理论基础。
二、调用和返回架构
主程序和子程序体系结构与面向对象系统都是调用-返回体系结构的子类型。
- 调用-返回架构使用分割-征服策略
- 将整个系统分为更小的子系统,以减少复杂度。
- 调用-返回的执行顺序被一个单线程控制
调用和返回体系结构的控制流
- 在调用一个子程序之后,控制权返回到调用者子程序。
- 软件组件只有得到了控制权之后,才能运行。
三、主程序和子程序
01 主子程序介绍
是一种Call and return 架构,代表结构化编程
- 主程序-子程序体系结构:
- 系统由一个主程序和一些分层组织的子程序组成;
- 主程序调用高层子程序;
- 高层子程序调用底层子程序
- 一个子例程的正确性通常取决于它调用的子程序是否正确
- 注:子程序(也称为过程)是较大程序中的一部分代码,它执行特定任务,相对独立于其余代码。子程序是现在的函数与方法的简单情况。
- 结构化的设计使用自顶向下的功能化设计
- 该系统从功能化的角度进行设计,从高级视图开始,逐步细化为更详细的设计。
- 系统依赖于一组子程序(函数),没有对象的概念
02 主子程序实例
药品成本计算问题
药品成本计算程序输入药品名称,程序将返回给定药品的单位数量成本
药品费用包括两部分——Research cost,and Production cost
结构化设计的优点
自顶向下的方法是非常成功的设计方法
对于10万行以下的代码的程序设计没有问题
结构化设计的缺点
当程序超过10万行代码的时候,该方法表现很差
原因
代码开发变得越来越慢
而且测试软件和保证其可靠性变得越来越困难
自顶向下功能化设计产生的的问题
功能化设计不容易进化(可扩展性差)
实际的系统很难按照功能化的观点刻画
功能化丢失了数据
功能化设计的程序复用性比较差
四、面向对象设计
是一种Call and return 架构
系统设计:系统可以看作是由一群对象组成
- 系统被视为对象的集合,而不是由互相调用的函数组成(后者是结构化设计的样式,e.g., C语言程序设计)。
- 每个对象都有自己的相关联的操作集。
面向对象设计的原理:基于信息隐藏
- 对象的定义
- 定义:一个对象是带有一个
- 状态(持有数据)和
- 语义(改变其状态的规则)
- 定义:一个对象是带有一个
- 面向对象的基本概念
- 对象被用于统一算法抽象与数据抽象的思想
- 基本概念
- 物体是用来统一思想的算法的抽象和数据抽象
- 对象将算法抽象与数据抽象封装(捆绑)在一起
面向对象设计的性质
- 封装
- 限制对某些信息的访问
- 私有变量;方法中的代码
- 继承(继承)
- 共享一个共享功能的定义
- Java类继承
- 动态绑定(动态绑定)
- 确定在运行时要调用的实际操作
- 不知道在程序运行之前调用哪个对象,在运行时将动态地决定使用哪个对象
- 复用与维护(重用和维护)
- 利用封装和局部性
面向对象设计的其它性质
- 对象是现实世界实体的抽象
对象之间互相独立
系统功能由对象服务表达
取消了共享数据区
对象可以是分布式的
对象可以按顺序执行或者并行执行
面向对象设计的优点
a) 程序容易维护 (Easier maintenance)
由于对象对其客户程序隐藏其表示,因此可以在不影响客户程序的情况下改变实现。对象可以理解为独立实体;
b) 对象是可复用的组件(Reusable);
c) 在一些系统中,对象是现实现实世界的映射;
d) 容易分解系统(Easy decomposition of a system) 对象将一组可访问的方法与其操作的数据捆绑在一起,
允许设计人员将问题分解为互相交互的对象的集合。
结构化设计与面向对象设计的区别
结构化设计结果是一颗树,其每个节点都是函数;这些函数按照一定的顺序调用,以便取得预期功能。
面向对象设计得到类图,由一群类组成;每个类都将相关的数据与施加于数据上的操作组织在一起。
五、案例研究
项目描述
要求设计一个关键词查找程序
1974年,Parnas提出了以下的问题:上下文中的关键词
(KWIC)索引系统
问题描述
程序接收一组有序的行;
每一行都是一组有序的单词;
每个单词都是一组有序的字符;
通过反复删除每行的第一个单词并将其附加到行的末尾,每个行都会发生循环移位
最后,将所有的已经移位的完成的行按照每行的首字母进行排序
KWIC索引系统输出所有的行
算法描述
1)输入一些行
Input
The sun is rising in the east
Flowers are blooming
2)进行圆圈移位 (产生多行)
After circular shift
The sun is rising in the east
sun is rising in the east the
is rising in the east the sun
rising in the east the sun is
in the east the sun is rising
the east the sun is rising in
east the sun is rising in the
Flowers are blooming
are blooming Flowers
blooming Flowers are
3)进行按照字母排序
After alphabetizer
are blooming Flowers
blooming Flowers are
east the sun is rising in the
flowers are blooming
in the east the sun is rising
is rising in the east the sun
rising in the east the sun is
sun is rising in the east the
the east the sun is rising in
The sun is rising in the east
(a) 案例一
用主程序-子程序架构设计KWIC系统
主/子例程架构的解决方案,用共享数据分解系统。根据所执行的四种基本功能:
input,
circularly Shift,
sort and
output.
这些函数被协调成子程序由一个主程序来完成
(b) 案例二
用面向对象系统设计KWIC系统
- 架构:将KWIC问题设计为以下的6个类:
- LineStorage 类
- 负责储存所有的行.
- Input 类
- 负责从一个文件中读入数据并且存储在LineStorage 对象中
- CircularShifter 类
- 负责对LineStorage中所储存的每个行的产生循环移位,然后将结果存储在CircularShifter对象中.
- Alphabetizer 类
- 负责对CircularShifter 对象中存储的行按照首字母进行排序.,之后,将结果写入Alphabetizer 对象中。
- Output 类
- 负责从Alphabetizer 对象中读出已经排序的行,并且打印出来
- LineStorage 类
- 关于数据共享
- 与主程序-子程序设计方案相反,在面向对象解决方案中,各个类之间不存在数据共享区;
- 相反,每个类都提供一个接口,该接口允许其他组件仅通过调用该接口中的方法来访问数据。
第四章 (1) 数据流系统与批处理架构
一、数据流软件体系结构的概念
01 数据流系统定义
- 定义:数据流系统是满足下列条件的系统,
- 数据的可用性控制着计算
- 设计的结构由数据在进程之间的有序运动决定的
- 数据的模式是明确的
02 数据流系统引入
- 概述
- 一般来说,数据可以以任意方式流动
- 当数据到达时,组件被激活,而无数据的时候,就休眠
- 我们对几乎线性的数据流系统感兴趣
- 或对比较简单,高度受限的循环结构感兴趣
- 关于控制流:我们关注程序中的控制流轨迹
- 数据可能伴随控制,但是数据不是主导
- 我们关系程序的执行顺序
- 设计方面的注意事项:数据是怎样流动的
- 随着设计流动,控制被激活
- 设计的考虑事项:数据的可用性、数据变换、数据延迟
03 数据流系统的开发方法论
-
将系统分解为一些模块将系统分解成模块
- 抽象隔离了复杂性
- 对数据对象的操作
-
将模块组合成图模块组装成一个图形
- 连接组成有向图的模块
- 接口协商匹配数据类型
-
运行该图运行图表
- 模块间数据流动
- scheduler自动化并行
- 内存管理器优化内存使用
二、批处理软件体系结构的概念
01 批处理系统的背景
1960年代-1970年代数据处理的例子
02 批处理系统的定义
批顺序处理是由一系列处理步骤组成的,中间是某种存储设备,如磁带、硬盘等。
每一步都将对输入存储执行一些操作,以获得一些有用的信息或修改源存储的内容,然后将结果数据保存到其接收器存储中。
03 批处理系统的性质
- 处理步骤是独立的程序(任何两个处理步骤之间没有交互)
- 每一步在彻底运行完成之后,下一步才能开始
- Proc1完整地处理完D1中的数据 => 将数据存入D2
- Proc2完整地处理完D2中的数据 => 将数据存入D3
- Proc3完整地处理完D3中的数据 => 将数据存入D4
- 数据作为一个整体在各个步骤之间传输
- 理解为一种特殊的数据流
- 数据以块状的形态流动
04 批处理系统的应用
- 早年的业务(商业)数据库处理方式
- 历史上,业务数据库处理主要由数据库更新控制:预定类型的离散交易;周期性定期报告;对错误请求的特殊处理。
- 历史数据库:批处理顺序
- 如何工作? => 大型机和磁带、每个步骤的手动区块调度计划
- 关系型数据库出现在1980年代
三、在批处理顺序体系结构中的程序设计实例
01 举例:千年虫与政治问题
在一个文本文件中,将所有年份表达式“xx”改为“xxxx”,例如“89”改为“1989”,将所有年份表达式“Republic China”改为“Taiwan”。
02 批处理架构在图像处理中的应用
基本知识:一幅图片使用一个 216 X 216;或者512 X 512; 或者其它的矩阵表示。
矩阵中的元素是0~255的正整数,叫做灰度值,0代表纯粹的黑色,255代表白色;
其余介于0与255之间的数值,代表灰色。
每一个元素具有一个特定的位置(x,y)和幅值f(x, y),这些元素就称为像素。
图像处理是按照一定的算法,对图像像素的灰度值进行变换。
设计考虑设计一个图像处理软件。该软件包含一些可以随时添加的过滤器(filters),例如
Blurring (图像模糊),
Sharpening (图像锐化)
Brightening (图像变亮)
EdgeDetector (发现图像边界),
一些过滤器被串联在一起,以便完成一些比较复杂的功能。
03 利用批处理架构设计的一个人口信息处理程序
项目需求:设计与实现一个人口信息处理程序。
将一个大的人口数据文件按照性别、年龄分为多个文件
使用批处理体系结构进行设计,如下图所示。
第四章 (2) 数据流系统与管道过滤器架构
一、管道-过滤器架构
01 管道-过滤器架构图示
02 管道-过滤器软件体系结构的定义
管道和过滤器架构由进行数据处理的过滤器和将数据从一个过滤器传送到下一个过滤器的管道组成。
此架构适合流数据的处理,例如宇宙飞船传回的数据。
- 过滤器的功能
- 添加信息——通过计算和添加信息来丰富数据
- 变换数据——通过改变表示来转换数据
- 流对流变换
- 不保留状态——在实例化之间不保留任何状态(忘记已经发生的事情)
- 管道的功能
- 将数据从筛选器的输出移到滤波器的输入
- 单向流
- 数据传输图
- 将数据从筛选器的输出移到滤波器的输入
- 整体运行机制
- 运行管道与过滤器,一直到没有任何计算
- 数据搬运是中介
二、批处理架构与管道-过滤器架构的比较
-
相似之处:处理过程之间互不调用 (independent processing modules)
-
区别
- 区别1
数据处理方式不同:
批处理架构,数据以 块状 形式传输,
管道-过滤器架构,数据以 流 的形式传输 - 区别 2
所处理的数据量不同 :
批处理架构,数据量是有限的
管道-过滤器架构,数据量是无限的
- 区别1
三、管道-过滤器架构的优缺点
- 优点
- 复用性好、容易扩展、容易修改
- 缺点
- 人-机交互差
- 纯粹的管道-过滤器架构设计的软件没有人-机交互;但是在实际应用中,可以改善此架构,
例如,对于每个过滤器,都可以增加一个用户图像界面
- 纯粹的管道-过滤器架构设计的软件没有人-机交互;但是在实际应用中,可以改善此架构,
- 浪费内存
- 由于每个过滤器必须不断地将数据(流)从输入端口复制到其输出端口,因此对于内存空间使用空间效率低下。
- 人-机交互差
第五章 数据流系统案例研究
问题描述:利用管道-过滤器体系结构设计一个旧文件更新系统
修复两千年问题:查找并更改所有的年份表达式,如“xy”为“19xy”,“89”为“1989”
修复政治问题:查找并更改所有“中华民国”为“台湾”
设计:使用Pipe-and-filter软件体系结构
- 设计四个过滤器:
- 投入、千年虫、政治、产出
每个过滤器处理数据并发送它到下一个过滤器。由三个管道连接四个过滤器。 - 控制:分布式控制。
- 只要有要计算的数据,每个筛选器就可以运行。过滤器之间的数据共享严格限制在管道上传输。
- 投入、千年虫、政治、产出
- 过滤器的功能
- Input filter: 连接到数据源文件Lagacy.txt
- 以字符流的方式读入输入文件的内容,一次只读一个字符;
- 检查、验证格式;
- 将合乎格式的内容写到下游管道in_y2,一次只写一个字符,确保空格与回行符也写入.
- Y2K filter: 连接到管道对象 in_y2
- 从in_y2中的管道读取字符流,每次只读一个字符;
- 遇到回行符,将字符组织成一行;
- 在这一行中,搜索Y2k问题并且修改该问题;
- 当一行内容修改完之后, 马上将内容写到管道对象y2_po, 一次只写一个字符,确保空格与回行符也写入.
- Politics filter: 连接到管道对象 y2_po.
- 从管道对象y2_po读取字符流,一次只读一个字符;
- 遇到回行符,暂停,将读到的内容组织成一行;
- 在这一行中,查找政治问题,并且修改;
- 然后,立即将修改过的内容以数据流的方式写到下游管道 po_ou, 一次只写一个字符,确保空格与回行符也写入.
- Output filter: 连接到管道对象po_ou,
- 从po_ou读取字符流
- 将它们写入标准输出。
- Input filter: 连接到数据源文件Lagacy.txt
- 管道的功能:
- in_y2管道:Input filter和Y2K filter共享,
- 这个管道是Input filter的输出管道和Y2K filter的输入管道。
- y2_po管道:Y2K过滤器和政治过滤器共享,
- 这个管道是Y2K过滤器的输出管道和Politics过滤器的输入管道。
- po_ou管道:在政治和输出过滤器之间共享,
- 这个管道是Politics过滤器的输出管道和output过滤器的输入管道。
- in_y2管道:Input filter和Y2K filter共享,
- 过滤器的设计
- 需要设计一个Java接口类Filter, 带有两个管道:输入管道与输出管道。再设计4个子类。
- 过滤器的结构
- 系统中的过滤器由名为Filter的抽象类表示。过滤器类的实例由以下部分组成:
- Input pipe 对象, 过滤器对象从这个管道对象中读取数据;
- Output pipe 对象, 过滤器对象将处理过的内容写入到这个输出对象。
- 系统中的过滤器由名为Filter的抽象类表示。过滤器类的实例由以下部分组成:
设计要求1:怎样使4个过滤器对象同时工作?
解决方案:利用多线程机制:让Filter类实现Java API的接口类Runnable
解决方案1:遗留文件更新
方法
Start():启动一个线程并运行线程
Run():在需要时由线程调用
Stop():停止一个线程
Transform():抽象方法
输入数据(输入过滤器),
查找并处理千年虫问题(使用千年虫过滤器),
发现和修复政治问题(在政治过滤器)
输出数据
运行过程:start => run => transform()
考虑满足设计要求2:数据像水流一样,从左至右流经各个管道与过滤器。
- Pipe 类的设计
- Pipe类的一个实例由两个流组成:输入流和输出流。
- 管道类通过以下方式连接这两个流。
- 写入输入流的数据被传输到输出流。
- 这样,这些数据就可以被一个过滤器对象从输出流中读取。
回答设计要求2:怎样使得数据像水流一样从左向右移动?
- 关于管道
- 管道中的数据量:管道对象限制其可以保存的数据量
- 数据消费. 每当客户程序从管道中读取数据时,这些数据都会被视为“已消费”,而这些被消费的数据占用的内存空间将被释放
- 管道对象是同步的:在特定时刻,只能有一个线程处理管道对象,即,当前只能有一个线程从管道对象写入或读取数据。
- 空管道、满管道:不能从一个空管道中读取数据;也不能向一个满管道中写入数据;如果遇到以上情况,那么从空管道读取或写入满管道的线程就会被阻止。
最后设计类图,可以作为一个模板使用
第六章 基于事件的软件体系结构
一、基于事件系统的概念
01 基于事件系统的定义
-
显式调用
- 传统上,在组件能提供子程序和函数集合的系统(如面向对象系统)中,各个组件通常通过显式调用这些子程序来相互交互。
- 显式调用的特点:调用者必须知道被调用者的类名、构造方法(包括参数)与要调用的方法(包括参数)
-
事件系统使用隐式调用
- 在基于事件的系统中,事件将调用某些过程,这些过程将自动运行。
- 事件系统使用隐式调用。
- 在基于事件的系统中,事件将调用一些过程,这些过程将自动运行。
- 事件系统使用隐式调用。
-
基于事件系统的定义
- 基于事件的系统是这样一种系统,在这种系统中,过程不会被直接调用(即,间接地或隐式地调用)。
-
什么是过程?
- 一个过程是程序设计语言中的一个模块,带有参数或者不带参数
- 它由过程调用执行。结果将是赋给调用参数,或在子例程中修改全局变量。
02 事件广播及处理机制
-
广播机制
- 组件可以广播一个或多个事件。
-
注册机制
- 系统中的其他组件可以在事件中注册感兴趣的内容
-
系统调用
- 一旦一个事件被广播,系统将自动调用为该事件注册的所有过程。(隐式地)
-
事件发布者可能不负责调用
03 汽车进口公司的预定与购买系统
基于事件的系统:事件广播、事件注册、方法被自动调用
04 显式调用与隐式调用的区别
05 组件结构
隐式调用风格中的组件是一个模块,其接口同时提供
一个过程集合(C++中的函数、java中的方法)
一个事件的集合
06 举例:使用事件系统架构设计的股票交易系统部分组件
07 集成开发环境的例子
考虑一个C、c++或Java的集成开发环境。这样的IDE由诸如编辑器、 源代码编辑器、变量监控器、变量监控、调试器、一个调试器等等。
这样的系统通常使用基于事件的体系结构。
编辑器和变量监视器注册调试器的断点事件。
当调试器运行到断点处而停止时,它会宣布一个事件,允许系统自动调用那些已注册的工具的过程。
这些过程可以—— ① 将编辑器滚动到源码的适当的行 ② 显示监视变量的值。
注意: ① 调试器仅仅广播了一个事件,但是不知道其它的组件将要做什么。 ② 松耦合。
二、事件处理策略
既然在事件系统中,当一个事件被广播了,系统将自动调用那些已经注册了的组件,那么就有了以下的问题。
问题:怎样将事件发送到已经注册了的组件中呢?
这个问题要分两种情况考虑
策略1:有独立事件调度模块的系统。
策略2:无中心事件调度模块的系统。
事件调度模块的责任
(1)接收事件
(2)分发事件
Strategy 1 有独立事件调度模块的系统
而调度模块以两种方式分发事件
(1)广播事件给系统的全部模块
调度程序可以向系统中的所有模块广播事件
(2)仅发送事件给那些已经注册了该事件的模块
调度员只是发送一个事件,这些模块,注册事件:发布/订阅策略(发布/订阅策略)
方式1:调度模块向全部模块广播事件
调度程序可以向系统中的所有模块广播事件
方式2:调度模块只向那些注册了该事件的模块发送事件
Strategy 2. 无独立事件调度模块的系统
这个模型通常被称为observable/Observer
每个模块都允许其它模块对其所发送的事件感兴趣
只将事件发送给注册者
三、使用观察者模式的设计的例子
01 实例描述
设计一个机场信息显示系统
机场信息显示系统的责任:
接收将要飞离机场与到达机场的航班信息并将这些信息通知给旅客,包括所有到港飞机和离港飞机的准时、延误等信息。
初步设计三个类
InforCenter类:收集所有的航班信息
VoiceInfo类:广播所有的航班信息给所有的旅客
WordInfo类:以文字的形式将所有的航班信息显示在屏幕上
02 实例类图
可理解该系统为一个事件系统(航班准时,延迟是事件)
利用观察者模式设计的机场信息发布系统
观察者模式可以被用来设计与实现比较简单的事件系统
观察者模式可以被认为是Observable/Observer 模型(无事件空间;事件空间由Observables产生)
03 实例交互
利用观察者模式设计的机场信息发布系统的典型交互
04 实例分析
-
设计亮点:两个界面
- Observable
Observer - 他们的所有方法都在设计中声明
- Observable
-
在InfoCenter中,需要实现以下所有方法:
- notifyObservers()
register(obs: Observer)
unRegister(obs:Observer)
- notifyObservers()
-
在VoiceInfo和WordInfo中,您需要实现该方法
- update(Observable subject, Object arg)