文章可能会有纰漏 将就看吧 emmm…
注: 本文章是有参考老师的ppt弄的 之后发现 东西 有点缺 就 开始了抄书 【设计模式的艺术_软件开发人员内功修炼之道】
本文适合 复习 与 看实例学习
简介
下图是分类 我这篇文章就按照目的分类写了
模式说明:
• 单例(Singleton)模式:
某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
• 原型(Prototype)模式:
将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
• 工厂方法(Factory Method)模式:
定义一个用于创建产品的接口,由子类决定生产什么产品。
• 抽象工厂(Abstract Factory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
• 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
• 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
• 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
• 桥接(Bridge)模式:将抽象与实现分离,使他们可以独立的变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这2个可变维度的耦合度。
• 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
• 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
• 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
• 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
• 模板方法(Template Method)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
• 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的改变不会影响到使用算法的客户。
• 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
• 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
• 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
• 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其它多个对象,从而影响其它对象的行为。
• 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
• 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
• 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
• 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
• 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
创建型模式
用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF中提供了5种创建型模式
单例模式
确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
结构
代码
public class Singleton {
private static Singleton instance=null; //静态私有成员变量
//私有构造函数
private Singleton() {
}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance() {
if(instance==null)
instance=new Singleton();
return instance;
}
}
就是创建对象的时候只能用同一个实例
饿汉式单例类(Eager Singleton)
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
懒汉式单例类
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
双重检索 加载
public class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//锁定代码块
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
instance = new LazySingleton(); //创建单例实例
}
}
}
return instance;
}
}
饿汉式单例类与懒汉式单例类的比较
饿汉式单例类:无须考虑多个线程同时访问的问题;调用速度和反应时间优于懒汉式单例;资源利用效率不及懒汉式单例;系统加载时间可能会比较长
懒汉式单例类:实现了延迟加载;必须处理好多个线程同时访问的问题;需通过双重检查锁定等机制进行控制,将导致系统性能受到一定影响
个人理解:
饿汉模式先提前加载好 以后可以直接调用 因为 直接用同一个 懒得进行判断 所以叫 懒汉
饿汉模式先不加载 以后可以调用的时候进行判断 创建 因为懒得加载所以叫饿汉
模式优点
提供了对唯一实例的受控访问
可以节约系统资源,提高系统的性能
允许可变数目的实例(多例类)
模式缺点
扩展困难(缺少抽象层)
单例类的职责过重
由于自动垃圾回收机制,可能会导致共享的单例对象的状态丢失
模式适用环境
系统只需要一个实例对象,或者因为资源消耗太大而只允许创建一个对象
客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例
简单工厂模式
简单工厂模式 (Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类
定义
在简单工厂模式中用于创建实例的方法通常是静态(static)方法,因此又被称为静态工厂方法(Static Factory Method)模式
要点:如果需要什么,只需要传入一个正确的参数,就可以获取所需要的对象,而无须知道其创建细节
结构
简单工厂模式包含以下3个角色:
Factory(工厂角色)
Product(抽象产品角色)
ConcreteProduct(具体产品角色)
代码
抽象产品类代码
public abstract class Product {
//所有产品类的公共业务方法
public void methodSame() {
//公共方法的实现
}
//声明抽象业务方法
public abstract void methodDiff();
}
具体产品类
public class ConcreteProduct extends Product{
//实现业务方法
public void methodDiff() {
//业务方法的实现
}
}
具体产品类可以做多个 但是因为是简单工厂
所以 建的只有两个 ConcreteProductA和ConcreteProduct B
进行一次判断就可以 如果是3个 就两次判断 以此类推
具体工厂方法
public class Factory {
//静态工厂方法
public static Product getProduct(String arg) {
Product product = null;
if (arg.equalsIgnoreCase("A")) {
product = new ConcreteProductA();
//初始化设置product
}
else if (arg.equalsIgnoreCase("B")) {
product = new ConcreteProductB();
//初始化设置product
}
return product;
}
}
客户端使用 代码
public class Client {
public static void main(String args[]) {
Product product;
product = Factory.getProduct("A"); //通过工厂类创建产品对象
product.methodSame();
product.methodDiff();
}
}
简单工厂的应用实例
某软件公司要基于Java语言开发一套图表库,该图表库可以为应用系统提供多种不同外观的图表,例如柱状图(HistogramChart)、饼状图(PieChart)、折线图(LineChart)等。该软件公司图表库设计人员希望为应用系统开发人员提供一套灵活易用的图表库,通过设置不同的参数即可得到不同类型的图表,而且可以较为方便地对图表库进行扩展,以便能够在将来增加一些新类型的图表。
现使用简单工厂模式来设计该图表库。
实例类图
实例代码
(1) Chart:抽象图表接口,充当抽象产品类
(2) HistogramChart:柱状图类,充当具体产品类
(3) PieChart:饼状图类,充当具体产品类
(4) LineChart:折线图类,充当具体产品类
(5) ChartFactory:图表工厂类,充当工厂类
(6) Client:客户端测试类
代码
抽象图表接口,充当抽象产品类
public interface Chart {
public void display();
}
柱状图类,充当具体产品类
public class HistogramChart implements Chart {
public HistogramChart() {
System.out.println("创建柱状图!");
}
public void display() {
System.out.println("显示柱状图!");
}
}
折线图类,充当具体产品类
public class LineChart implements Chart {
public LineChart() {
System.out.println("创建折线图!");
}
public void display() {
System.out.println("显示折线图!");
}
}
饼状图类,充当具体产品类
public class PieChart implements Chart {
public PieChart() {
System.out.println("创建饼状图!");
}
public void display() {
System.out.println("显示饼状图!");
}
}
图表工厂类,充当工厂类
public class ChartFactory {
//静态工厂方法
public static Chart getChart(String type) {
Chart chart = null;
if (type.equalsIgnoreCase("histogram")) {
chart = new HistogramChart();
System.out.println("初始化设置柱状图!");
}
else if (type.equalsIgnoreCase("pie")) {
chart = new PieChart();
System.out.println("初始化设置饼状图!");
}
else if (type.equalsIgnoreCase("line")) {
chart = new LineChart();
System.out.println("初始化设置折线图!");
}
return chart;
}
}
客户端类
public class Client {
public static void main(String args[]) {
Chart chart;
//通过静态工厂方法创建产品
chart = ChartFactory.getChart("histogram");
chart.display();
}
}
结果
模式优点
实现了对象创建和使用的分离
客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可
可以通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性
模式缺点
工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响
增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度
系统扩展困难,一旦添加新产品不得不修改工厂逻辑
由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构,工厂类不能得到很好地扩展
模式适用环境
工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂
客户端只知道传入工厂类的参数,对于如何创建对象并不关心
工厂方法模式
工厂模式与简单工厂模式的区别
工厂方法模式:
不再提供一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成
如果出现新的按钮类型,只需要为这种新类型的按钮定义一个具体的工厂类就可以创建该新按钮的实例
定义
简称为工厂模式(Factory Pattern)
又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)
工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象
目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类
结构
工厂方法模式包含以下4个角色:
Product(抽象产品)
ConcreteProduct(具体产品)
Factory(抽象工厂)
ConcreteFactory(具体工厂)
代码
抽象工厂类代码
public interface Factory {
public Product factoryMethod();
}
具体工厂类代码
public class ConcreteFactory implements Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
客户端代码片段
Factory factory;
factory = new ConcreteFactory(); //可通过配置文件和反射机制实现
Product product;
product = factory.factoryMethod();
工厂方法模式的应用实例
某系统运行日志记录器(Logger)可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。
为了更好地封装记录器的初始化过程并保证多种记录器切换的灵活性,现使用工厂方法模式设计该系统。
实例类图
实例代码
(1) Logger:日志记录器接口,充当抽象产品角色
(2) DatabaseLogger:数据库日志记录器,充当具体产品角色
(3) FileLogger:文件日志记录器,充当具体产品角色
(4) LoggerFactory:日志记录器工厂接口,充当抽象工厂角色
(5) DatabaseLoggerFactory:数据库日志记录器工厂类,充当具体工厂角色
(6) FileLoggerFactory:文件日志记录器工厂类,充当具体工厂角色
(7) Client:客户端测试类
代码
日志记录器接口:抽象产品
public interface ILogger
{
void WriteLog();
}
文件日志记录器:具体产品
public class FileLogger : ILogger{
public void WriteLog()
{
Console.WriteLine("文件日志记录!");
}
}
数据库日志记录器:具体产品
public class DatabaseLogger : ILogger{
public void WriteLog()
{
Console.WriteLine("数据库日志记录!");
}
日志记录器工厂接口:抽象工厂
public interface ILoggerFactory
{
ILogger CreateLogger();
}
文件日志记录器工厂类:具体工厂
public class FileLoggerFactory : ILoggerFactory
{
public ILogger CreateLogger()
{
//创建文件日志记录器对象
var logger = new FileLogger();
//创建文件,省略代码
return logger;
}
}
数据库日志记录器工厂类:具体工厂
public class DatabaseLoggerFactory : ILoggerFactory
{
public ILogger CreateLogger()
{
//连接数据库,代码省略
//创建数据库日志记录器对象
var logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
}
客户端测试代码
class Program
{
static void Main(string[] args)
{
var factory = new FileLoggerFactory();//可引入配置文件实现
var logger = factory.CreateLogger();
logger.WriteLog();
}
}
上面这个代码 不太靠谱 以后会改的 如果没改 记得评论提醒我
开始抄书 干了 !!!!!!
模式优点
工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节
能够让工厂自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部
在系统中加入新产品时,完全符合开闭原则
模式缺点
系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销
增加了系统的抽象性和理解难度
模式适用环境
客户端不知道它所需要的对象的类(客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体产品对象由具体工厂类创建)
抽象工厂类通过其子类来指定创建哪个对象
抽象工厂模式
定义
又称为工具(Kit)模式
抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品
当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、更有效率
结构
抽象工厂模式的结构
抽象工厂模式包含以下4个角色:
AbstractFactory(抽象工厂)
ConcreteFactory(具体工厂)
AbstractProduct(抽象产品)
ConcreteProduct(具体产品)
代码
典型的抽象工厂类代码:
public interface AbstractFactory {
public AbstractProductA createProductA(); //工厂方法一
public AbstractProductB createProductB(); //工厂方法二
……
}
典型的具体工厂类代码:
public class ConcreteFactory1 implements AbstractFactory {
//工厂方法一
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
//工厂方法二
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
抽象工厂模式的应用实例
实例代码
(1) Button:按钮接口,充当抽象产品
(2) SpringButton:Spring按钮类,充当具体产品
(3) SummerButton:Summer按钮类,充当具体产品
(4) TextField:文本框接口,充当抽象产品
(5) SpringTextField:Spring文本框类,充当具体产品
(6) SummerTextField:Summer文本框类,充当具体产品
(7) ComboBox:组合框接口,充当抽象产品
(8) SpringComboBox:Spring组合框类,充当具体产品
(9) SummerComboBox:Summer组合框类,充当具体产品
(10) SkinFactory:界面皮肤工厂接口,充当抽象工厂
(11) SpringSkinFactory:Spring皮肤工厂,充当具体工厂
(12) SummerSkinFactory:Summer皮肤工厂,充当具体工厂
(13) Client:客户端测试类
代码
/// <summary>
/// 按钮接口:抽象产品
/// </summary>
public interface Button
{
void Display();
}
/// <summary>
/// Spring按钮类:具体产品
/// </summary>
public class SpringButton : Button
{
public void Display()
{
Console.WriteLine("显示Spring风格按钮");
}
}
/// <summary>
/// Summer按钮类:具体产品
/// </summary>
public class SummerButton : Button
{
public void Display()
{
Console.WriteLine("显示Summer风格按钮");
}
}
/// <summary>
/// 文本框接口:抽象产品
/// </summary>
public interface TextField
{
void Display();
}
/// <summary>
/// Spring 文本框类:具体产品
/// </summary>
public class SpringTextField : TextField
{
public void Display()
{
Console.WriteLine("显示Spring风格文本框");
}
}
/// <summary>
/// Summer 文本框类:具体产品
/// </summary>
public class SummerTextField : TextField
{
public void Display()
{
Console.WriteLine("显示Summer风格文本框");
}
}
/// <summary>
/// 组合框接口:抽象产品
/// </summary>
public interface ComboBox
{
void Display();
}
/// <summary>
/// Spring组合框类:具体产品
/// </summary>
public class SpringComboBox : ComboBox
{
public void Display()
{
Console.WriteLine("显示Spring风格组合框");
}
}
/// <summary>
/// Summer 组合框类:具体产品
/// </summary>
public class SummerComboBox : ComboBox
{
public void Display()
{
Console.WriteLine("显示Summer风格组合框");
}
}
/// <summary>
/// 界面皮肤工厂接口:抽象工厂
/// </summary>
public interface ISkinFactory
{
Button CreateButton();
TextField CreateTextField();
ComboBox CreateComboBox();
}
/// <summary>
/// Spring 皮肤工厂:具体工厂
/// </summary>
public class SpringSkinFactory : ISkinFactory
{
public Button CreateButton()
{
return new SpringButton();
}
public TextField CreateTextField()
{
return new SpringTextField();
}
public ComboBox CreateComboBox()
{
return new SpringComboBox();
}
}
/// <summary>
/// Summer 皮肤工厂:具体皮肤工厂
/// </summary>
public class SummerSkinFactory : ISkinFactory
{
public Button CreateButton()
{
return new SummerButton();
}
public TextField CreateTextField()
{
return new SummerTextField();
}
public ComboBox CreateComboBox()
{
return new SummerComboBox();
}
}
为了让系统更具备良好的灵活性和可扩展性,这里依旧将具体工厂添加到配置文件中,并添加配置文件的帮助类,具体代码如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--value为具体工厂类的完全限定名(命名空间+类名)-->
<add key="SkinFactory" value="LXP.DesignPattern.AbstractFactory.SpringSkinFactory"/>
</appSettings>
</configuration>
帮助类代码:
public class AppConfigHelper
{
/// <summary>
/// 获取具体皮肤工厂方法
/// </summary>
/// <returns></returns>
public static object GetSkinFactory()
{
try
{
var skinFactoryName = ConfigurationManager.AppSettings["SkinFactory"];
var type = Type.GetType(skinFactoryName);
return type == null ? null : Activator.CreateInstance(type);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
}
客户端测试代码:
class Program
{
static void Main(string[] args)
{
var skinFactory = (ISkinFactory) AppConfigHelper.GetSkinFactory();
var button = skinFactory.CreateButton();
var textField = skinFactory.CreateTextField();
var comboBox = skinFactory.CreateComboBox();
button.Display();
textField.Display();
comboBox.Display();
}
}
输出结果
这个抄的 …原文链接
模式优点
隔离了具体类的生成,使得客户端并不需要知道什么被创建
当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象
增加新的产品族很方便,无须修改已有系统,符合开闭原则
模式缺点
增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了开闭原则
模式适用环境
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节
系统中有多于一个的产品族,但每次只使用其中某一产品族
属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来
产品等级结构稳定,在设计完成之后不会向系统中增加新的产品等级结构或者删除已有的产品等级结构
生成器模式
定义
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
将客户端与包含多个部件的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需建造者的类型即可
如何逐步创建一个复杂的对象,不同的建造者定义了不同的创建过程
结构
建造者模式包含以下4个角色:
Builder(抽象建造者)
ConcreteBuilder(具体建造者)
Product(产品)
Director(指挥者)
代码
典型的复杂对象类代码:
public class Product {
private String partA; //定义部件,部件可以是任意类型,包括值类型和引用类型
private String partB;
private String partC;
//partA的Getter方法和Setter方法省略
//partB的Getter方法和Setter方法省略
//partC的Getter方法和Setter方法省略
}
典型的抽象建造者类代码:
public abstract class Builder {
//创建产品对象
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public Product getResult() {
return product;
}
}
典型的具体建造者类代码:
public class ConcreteBuilder1 extends Builder{
public void buildPartA() {
product.setPartA("A1");
}
public void buildPartB() {
product.setPartB("B1");
}
public void buildPartC() {
product.setPartC("C1");
}
}
典型的指挥者类代码:
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder=builder;
}
public void setBuilder(Builder builder) {
this.builder=builer;
}
//产品构建与组装方法
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
客户类代码片段:
……
Builder builder = new ConcreteBuilder1(); //可通过配置文件实现
Director director = new Director(builder);
Product product = director.construct();
……
应用实例
某游戏软件公司决定开发一款基于角色扮演的多人在线网络游戏,玩家可以在游戏中扮演虚拟世界中的一个特定角色,角色根据不同的游戏情节和统计数据(例如力量、魔法、技能等)具有不同的能力,角色也会随着不断升级而拥有更加强大的能力。
作为该游戏的一个重要组成部分,需要对游戏角色进行设计,而且随着该游戏的升级将不断增加新的角色。通过分析发现,游戏角色是一个复杂对象,它包含性别、面容等多个组成部分,不同类型的游戏角色,其性别、面容、服装、发型等外部特性都有所差异,例如“天使”拥有美丽的面容和披肩的长发,并身穿一袭白裙;而“恶魔”极其丑陋,留着光头并穿一件刺眼的黑衣。
无论是何种造型的游戏角色,它的创建步骤都大同小异,都需要逐步创建其组成部分,再将各组成部分装配成一个完整的游戏角色。现使用建造者模式来实现游戏角色的创建。
实例代码
(1) Actor:游戏角色类,充当复杂产品对象
(2) ActorBuilder:游戏角色建造者,充当抽象建造者
(3) HeroBuilder:英雄角色建造者,充当具体建造者
(4) AngelBuilder:天使角色建造者,充当具体建造者
(5) DevilBuilder:恶魔角色建造者,充当具体建造者
(6) ActorController:角色控制器,充当指挥者
(7) Client:客户端测试类
代码
先空着 有空 补
模式优点
客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
每一个具体建造者都相对独立,与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,扩展方便,符合开闭原则
可以更加精细地控制产品的创建过程
模式缺点
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,不适合使用建造者模式,因此其使用范围受到一定的限制
如果产品的内部变化复杂,可能会需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,增加了系统的理解难度和运行成本
模式适用环境
需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量
需要生成的产品对象的属性相互依赖,需要指定其生成顺序
对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中
隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品
原型模式
定义
原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
工作原理:将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现创建过程
创建新对象(也称为克隆对象)的工厂就是原型类自身,工厂方法由负责复制原型对象的克隆方法来实现
通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,每一个克隆对象都是独立的
通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不完全相同的对象
结构
原型模式包含以下3个角色:
Prototype(抽象原型类)
ConcretePrototype(具体原型类)
Client(客户类)
浅克隆与深克隆
浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制
深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也将被复制
代码
接口
public interface Prototype {
public Prototype clone();
}
public class ConcretePrototype implements Prototype {
private String attr;
public void setAttr(String attr) {
this.attr = attr;
}
public String getAttr() {
return this.attr;
}
//克隆方法
public Prototype clone() {
Prototype prototype = new ConcretePrototype(); //创建新对象
prototype.setAttr(this.attr);
return prototype;
}
}
客户端片段
……
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAttr("Sunny");
ConcretePrototype copy = (ConcretePrototype)prototype.clone();
……
个人观点 其实就是 类里面 多一个clone的方法
方法中 创造一个新类
将当前类的所有属性值都复制到新类中 就 相当于克隆了
Java语言中的clone()方法和Cloneable接口
在Java语言中,提供了一个clone()方法用于实现浅克隆,该方法使用起来很方便,直接调用super.clone()方法即可实现克隆
方法实例.
在使用某OA系统时,有些岗位的员工发现他们每周的工作都大同小异,因此在填写工作周报时很多内容都是重复的,为了提高工作周报的创建效率,大家迫切希望有一种机制能够快速创建相同或者相似的周报,包括创建周报的附件。
试使用原型模式对该OA系统中的工作周报创建模块进行改进
工作作周报WeeklyLog:具体原型类,考虑到代码的可读性和易理解性,只列出部分与模式相关的核心代码
class WeeklyLog implenents Cloneable { private Str ing name;
private String date;
private String content;
public void setName(String name) {
this. name = name;
public void setDate(String date) {
this. date = date;
public void setContent(String content) {
this. content = content;
public String getName() {
return (thia. name):
}
publ te String getDste() {
return (thte. date);
}
public String getContent() (
return (this. content);
}
//克隆方法clone(),此处使用Jave语言提供的克隆机制
public WeeklyLog clone() {
Object obj=null;
try{
obj=super.clone();
return (WeeklyLog)obj;
}
catch(CloneNotSupportEDexception e){
System.out. println("不支持复制!");
return null;
}
}
}
编写如下客户端测试代码;
… 写答案好麻烦 下面的就先不写了
模式优点
简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
扩展性较好
提供了简化的创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作
模式缺点
需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦
模式适用环境
创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
系统要保存对象的状态,而对象的状态变化很小
需要避免使用分层次的工厂类来创建分层次的对象
行为型模式
用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。GoF中提供了11种行为型模式。
行为型模式(Behavioral Pattern) 关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责
行为型模式:不仅仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分
责任(职责)链模式
定义
将请求的处理者组织成一条链,并让请求沿着链传递,由链上的处理者对请求进行相应的处理
客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上,将请求的发送者和请求的处理者解耦
结构
职责链模式包含以下两个角色:
Handler(抽象处理者)
ConcreteHandler(具体处理者
代码
典型的抽象处理者代码
public abstract class Handler {
//维持对下家的引用
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor=successor;
}
public abstract void handleRequest(String request);
}
具体处理者代码
public class ConcreteHandler extends Handler {
public void handleRequest(String request) {
if (请求满足条件) {
//处理请求
}
else {
this.successor.handleRequest(request); //转发请求
}
}
}
典型的客户端代码
……
Handler handler1, handler2, handler3;
handler1 = new ConcreteHandlerA();
handler2 = new ConcreteHandlerB();
handler3 = new ConcreteHandlerC();
//创建职责链
handler1.setSuccessor(handler2);
handler2.setSuccessor(handler3);
//发送请求,请求对象通常为自定义类型
handler1.handleRequest("请求对象");
……
应用实例
实例代码
(1) PurchaseRequest:采购单类,充当请求类
(2) Approver:审批者类,充当抽象处理者
(3) Director:主任类,充当具体处理者
(4) VicePresident:副董事长类,充当具体处理者
(5) President:董事长类,充当具体处理者
(6) Congress:董事会类,充当具体处理者
(7) Client:客户端测试类
暂无
纯与不纯的职责链模式
纯的职责链模式
一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家
不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况
一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况
不纯的职责链模式
允许某个请求被一个具体处理者部分处理后向下传递,或者一个具体处理者处理完某请求后其后继处理者可以继续处理该请求
一个请求可以最终不被任何处理者对象所接收并处理
JavaScript的事件浮升(Event Bubbling)处理机制
模式优点
使得一个对象无须知道是其他哪一个对象处理其请求,降低了系统的耦合度
可简化对象之间的相互连接
给对象职责的分配带来更多的灵活性
增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可
模式缺点
不能保证请求一定会被处理
对于比较长的职责链,系统性能将受到一定影响,在进行代码调试时不太方便
如果建链不当,可能会造成循环调用,将导致系统陷入死循环
模式适用环境
有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定
在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
可动态指定一组对象处理请求
命令模式
定义
命令模式别名为动作(Action)模式或事务(Transaction)模式:将一个请求封装为一个对象,从而让你可以用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作
结构
命令模式包含以下4个角色:
Command(抽象命令类)
ConcreteCommand(具体命令类)
Invoker(调用者)
Receiver(接收者)
代码
命令模式的实现
命令模式的本质是对请求进行封装
一个请求对应于一个命令,将发出命令的责任和执行命令的责任分开
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的
典型的抽象命令类代码:
public abstract class Command {
public abstract void execute();
}
典型的调用者(请求发送者)类代码:
public class Invoker {
private Command command;
//构造注入
public Invoker(Command command) {
this.command = command;
}
//设值注入
public void setCommand(Command command) {
this.command = command;
}
//业务方法,用于调用命令类的execute()方法
public void call() {
command.execute();
}
}
典型的请求接收者类代码:
public class Receiver {
public void action() {
//具体操作
}
}
应用实例
为了用户使用方便,某系统提供了一系列功能键,用户可以自定义功能键的功能,例如功能键FunctionButton可以用于退出系统(由SystemExitClass类来实现),也可以用于显示帮助文档(由DisplayHelpClass类来实现)。
用户可以通过修改配置文件来改变功能键的用途,现使用命令模式来设计该系统,使得功能键类与功能类之间解耦,可为同一个功能键设置不同的功能。
实例代码
(1) FunctionButton:功能键类,充当请求调用者(请求发送者)
(2) Command:抽象命令类
(3) ExitCommand:退出命令类,充当具体命令类
(4) HelpCommand:帮助命令类,充当具体命令类
(5) SystemExitClass:退出系统模拟实现类,充当请求接收者
(6) DisplayHelpClass:显示帮助文档模拟实现类,充当请求接收者
(7) Client:客户端测试类
增(8)FBSeltitingWindow:功能键窗口类
代码
package 命令模式;
import java.util.*;
//功能键设计窗口类
public class FBSettingWindow {
private String tiele;//窗口标题
//定义一个ArrayList来存储所有的功能键
private ArrayList<FunctionButton> functionButtons=new ArrayList<FunctionButton>();
public FBSettingWindow(String tiele) {
this.tiele = tiele;
}
public String getTiele() {
return tiele;
}
public void setTiele(String tiele) {
this.tiele = tiele;
}
public void addFunctionButton(FunctionButton fb) {
functionButtons.add(fb);
}
public void removeFunctionButton(FunctionButton fb) {
functionButtons.remove(fb);
}
//显示窗口及功能键
public void display(){
System.out.println("显示窗口:"+this.tiele);
System.out.println("显示功能键:");
for (Object obj:functionButtons){
System.out.println(((FunctionButton)obj).getName());
}
System.out.println("---------------------------------------------------------------");
}
}
//功能键类 :请求发送者
class FunctionButton {
private String name; //功能键名称
private Command command;//维持一个抽象命令对象的引用
public FunctionButton(String name) {
this.name = name;
}
public String getName() {
return name;
}
//为功能键注入命令
public void setCommand(Command command) {
this.command = command;
}
//发送请求的方法
public void onClick(){
System.out.print("点击功能键:");
command.execute();
}
}
//抽象命令类
abstract class Command {
public abstract void execute();
}
//帮助命令类:具体命令类
class HelpCommand extends Command{
private HelpHandler hhObj;//维护对请求接收者的引用
public HelpCommand() {
this.hhObj = new HelpHandler();
}
@Override
public void execute() {
hhObj.display();
}
}
//最小化命令类:具体命令类
class MinimzeCommand extends Command{
private WindowHandler whObj;//维护对请求接收者的引用
public MinimzeCommand() {
this.whObj = new WindowHandler();
}
//命令执行方法,将调用请求接收者的业务方法
@Override
public void execute() {
whObj.minimize();
}
}
//窗口处理类:请求接收者
class WindowHandler{
public void minimize(){
System.out.println("将窗口最小化值托盘");
}
}
//帮助文档处理类:请求接收者
class HelpHandler{
public void display(){
System.out.println("显示帮助文档!");
}
}
config.xml
<?xml version="1.0" ?>
<config>
<className>mingling.HelpCommand</className>
<className>mingling.MinimzeCommand</className>
</config>
XMLUtil读取类
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
public class XMLUtil {
//该方法用于从XML文件中提取具体类类名,并返回一个实例对象,可以通过参数的不同返回不同的类
//同类名节点所对应的实例
public static Object getBean(int i){
try {
//创建文档对象
DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=dFactory.newDocumentBuilder();
Document doc;
doc=builder.parse(new File("src/mingling/config.xml"));
//获取包含类名的文本节点
NodeList nl=doc.getElementsByTagName("className");
Node classNode=null;
if (0 ==i){
classNode =nl.item(0).getFirstChild();
}else {
classNode =nl.item(1).getFirstChild();
}
String cName=classNode.getNodeValue();
//同类名生成实例对象并将其返回、
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
客户端代码
public class Client {
public static void main(String[] args) {
FBSettingWindow fbsw=new FBSettingWindow("功能键设置");
FunctionButton fb1,fb2;
fb1=new FunctionButton("功能键1");
fb2=new FunctionButton("功能键2");
Command command1,command2;
//通过读取配置文件和反射生成具体命令对象
command1=(Command)XMLUtil.getBean(0);
command2=(Command)XMLUtil.getBean(1);
//将命令对象注入功能键
fb1.setCommand(command1);
fb2.setCommand(command2);
fbsw.addFunctionButton(fb1);
fbsw.addFunctionButton(fb2);
fbsw.display();
//调用功能键的业务方法
fb1.onClick();
fb2.onClick();
}
}
结果
记录请求日志
动机
将请求的历史记录保存下来,通常以日志文件(Log File)的形式永久存储在计算机中
为系统提供一种恢复机制
可以用于实现批处理
防止因为断电或者系统重启等原因造成请求丢失,而且可以避免重新发送全部请求时造成某些命令的重复执行
实现命令队列
将发送请求的命令对象通过序列化写到日志文件中
命令类必须实现接口Serializable
import java.util.*;
public class CommandQueue {
//定义一个ArrayList来存储命令队列
private ArrayList<Command> commands = new ArrayList<Command>();
public void addCommand(Command command) {
commands.add(command);
}
public void removeCommand(Command command) {
commands.remove(command);
}
//循环调用每一个命令对象的execute()方法
public void execute() {
for (Object command : commands) {
((Command)command).execute();
}
}
}
实例
可以通过对命令类进行修改使得系统支持撤销(Undo)操作和恢复(Redo)操作
模式优点
降低系统的耦合度
新的命令可以很容易地加入到系统中,符合开闭原则
可以比较容易地设计一个命令队列或宏命令(组合命令)
为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案
模式缺点
使用命令模式可能会导致某些系统有过多的具体命令类(针对每一个对请求接收者的调用操作都需要设计一个具体命令类)
模式适用环境
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
系统需要在不同的时间指定请求、将请求排队和执行请求
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作
系统需要将一组操作组合在一起形成宏命令
迭代器模式
定义
迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素,且不用暴露该对象的内部表示。
又名游标(Cursor)模式
通过引入迭代器,客户端无须了解聚合对象的内部结构即可实现对聚合对象中成员的遍历,还可以根据需要很方便地增加新的遍历方式
结构
迭代器模式包含以下4个角色:
Iterator(抽象迭代器)
ConcreteIterator(具体迭代器)
Aggregate(抽象聚合类)
ConcreteAggregate(具体聚合类)
代码
典型的抽象迭代器代码
public interface Iterator {
public void first(); //将游标指向第一个元素
public void next(); //将游标指向下一个元素
public boolean hasNext(); //判断是否存在下一个元素
public Object currentItem(); //获取游标指向的当前元素
}
抽象类代码
public interface Aggregate {
Iterator createIterator();
}
具体集合类代码
public class ConcreteAggregate implements Aggregate {
......
public Iterator createIterator() {
return new ConcreteIterator(this);
}
......
}
结构
public class ConcreteIterator implements Iterator {
private ConcreteAggregate objects; //维持一个对具体聚合对象的引用,以便于访问存储在聚合对象中的数据
private int cursor; //定义一个游标,用于记录当前访问位置
public ConcreteIterator(ConcreteAggregate objects) {
this.objects=objects;
}
public void first() { ...... }
public void next() { ...... }
public boolean hasNext() { ...... }
public Object currentItem() { ...... }
}
实例
某软件公司为某商场开发了一套销售管理系统,在对该系统进行分析和设计时,开发人员发现经常需要对系统中的商品数据、客户数据等进行遍历,为了复用这些遍历代码,开发人员设计了一个抽象的数据集合类AbstractObjectList,将存储商品和客户等数据的类作为其子类,AbstractObjectList类结构如下图所示:
AbstractObjectList类的方法与说明
AbstractObjectList类的子类ProductList和CustomerList分别用于存储商品数据和客户数据。
通过分析,发现AbstractObjectList类的职责非常重,它既负责存储和管理数据,又负责遍历数据,违背了单一职责原则,实现代码将非常复杂。因此,开发人员决定使用迭代器模式对AbstractObjectList类进行重构,将负责遍历数据的方法提取出来,封装到专门的类中,实现数据存储和数据遍历分离,还可以给不同的具体数据集合类提供不同的遍历方式。
现给出使用迭代器模式重构后的解决方案。
实例结构
(1) AbstractObjectList:抽象聚合类
(2) ProductList:商品数据类,充当具体聚合类
(3) AbstractIterator:抽象迭代器
(4) ProductIterator:商品迭代器,充当具体迭代器
(5) Client:客户端测试类
代码
待补
内置迭代器
结构
实现
java.util.Collection
package java.util;
public interface Collection<E> extends Iterable<E> {
……
boolean add(Object c);
boolean addAll(Collection c);
boolean remove(Object o);
boolean removeAll(Collection c);
boolean remainAll(Collection c);
Iterator iterator();
……
}
java.util.Iterator
package java.util;
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
客户端
import java.util.*;
public class IteratorDemo {
public static void process(Collection c) {
Iterator i = c.iterator(); //创建迭代器对象
//通过迭代器遍历聚合对象
while(i.hasNext()) {
System.out.println(i.next().toString());
}
}
public static void main(String args[]) {
Collection persons;
persons = new ArrayList(); //创建一个ArrayList类型的聚合对象
persons.add("张无忌");
persons.add("小龙女");
persons.add("令狐冲");
persons.add("韦小宝");
persons.add("袁紫衣");
persons.add("小龙女");
process(persons);
}
}
模式优点
支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式
简化了聚合类
由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,符合开闭原则
模式缺点
在增加新的聚合类时需要对应地增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性
抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是一件很容易的事情
模式适用环境
访问一个聚合对象的内容而无须暴露它的内部表示
需要为一个聚合对象提供多种遍历方式
为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口
访问者模式
定义
访问者模式:表示一个作用于某对象结构中的各个元素的操作。访问者模式让你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
它为操作存储不同类型元素的对象结构提供了一种解决方案
用户可以对不同类型的元素施加不同的操作
结构
访问者模式包含以下5个角色:
Visitor(抽象访问者)
ConcreteVisitor(具体访问者)
Element(抽象元素)
ConcreteElement(具体元素)
ObjectStructure(对象结构)
代码
典型的抽象访问者类代码
public abstract class Visitor {
public abstract void visit(ConcreteElementA elementA);
public abstract void visit(ConcreteElementB elementB);
public void visit(ConcreteElementC elementC) {
//元素ConcreteElementC操作代码
}
}
典型的具体访问者类代码:
public class ConcreteVisitor extends Visitor {
public void visit(ConcreteElementA elementA) {
//元素ConcreteElementA操作代码
}
public void visit(ConcreteElementB elementB) {
//元素ConcreteElementB操作代码
}
}
典型的抽象元素类代码:
public interface Element {
public void accept(Visitor visitor);
}
典型的具体元素类代码:
public class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA() {
//业务方法
}
}
应用实例
某公司OA系统中包含一个员工信息管理子系统,该公司员工包括正式员工和临时工,每周人力资源部和财务部等部门需要对员工数据进行汇总,汇总数据包括员工工作时间、员工工资等。该公司基本制度如下:
(1) 正式员工每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同;如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假所扣工资以80元/小时计算,直到基本工资扣除到零为止。除了记录实际工作时间外,人力资源部需记录加班时长或请假时长,作为员工平时表现的一项依据。
(2) 临时工每周工作时间不固定,基本工资按小时计算,不同岗位的临时工小时工资不同。人力资源部只需记录实际工作时间。
人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。
现使用访问者模式设计该系统,绘制类图并使用Java语言编码实现。
结构
实例代码
(1) Employee:员工类,充当抽象元素类
(2) FulltimeEmployee:全职员工类,充当具体元素类
(3) ParttimeEmployee:兼职员工类,充当具体元素类
(4) Department:部门类,充当抽象访问者类
(5) FADepartment:财务部类,充当具体访问者类
(6) HRDepartment:人力资源部类,充当具体访问者类
(7) EmployeeList:员工列表类,充当对象结构
(8) Client:客户端测试类
代码
wu
模式优点
增加新的访问操作很方便
将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中,类的职责更加清晰
让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作
模式缺点
增加新的元素类很困难
破坏了对象的封装性
模式适用环境
一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作
需要对一个对象结构中的对象进行很多不同的且不相关的操作,并需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类
对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作
观察者模式
分析
软件系统:一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变,它们之间将产生联动
观察者模式:
定义了对象之间一种一对多的依赖关系,让一个对象的改变能够影响其他对象
发生改变的对象称为观察目标,被通知的对象称为观察者
一个观察目标可以对应多个观察者
定义
观察者模式:定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。
别名
发布-订阅(Publish/Subscribe)模式
模型-视图(Model/View)模式
源-监听器(Source/Listener)模式
从属者(Dependents)模式
结构
观察者模式包含以下4个角色:
Subject(目标)
ConcreteSubject(具体目标)
Observer(观察者)
ConcreteObserver(具体观察者)
代码
典型的抽象目标代码
import java.util.*;
public abstract class Subject {
//定义一个观察者集合用于存储所有观察者对象
protected ArrayList observers<Observer> = new ArrayList();
//注册方法,用于向观察者集合中增加一个观察者
public void attach(Observer observer) {
observers.add(observer);
}
//注销方法,用于在观察者集合中删除一个观察者
public void detach(Observer observer) {
observers.remove(observer);
}
//声明抽象通知方法
public abstract void notify();
}
典型的具体目标类代码
public class ConcreteSubject extends Subject {
//实现通知方法
public void notify() {
//遍历观察者集合,调用每一个观察者的响应方法
for(Object obs:observers) {
((Observer)obs).update();
}
}
}
典型的抽象观察者代码:
public interface Observer {
//声明响应方法
public void update();
}
典型的具体观察者代码:
public class ConcreteObserver implements Observer {
//实现响应方法
public void update() {
//具体响应代码
}
}
典型客户端代码
……
Subject subject = new ConcreteSubject();
Observer observer = new ConcreteObserver();
subject.attach(observer); //注册观察者
subject.notify();
……
实例
在某多人联机对战游戏中,多个玩家可以加入同一战队组成联盟,当战队中的某一成员受到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将做出响应。
现使用观察者模式设计并实现该过程,以实现战队成员之间的联动
结构
实例代码
(1) AllyControlCenter:指挥部(战队控制中心)类,充当抽象目标类
(2) ConcreteAllyControlCenter:具体指挥部类,充当具体目标类
(3) Observer:抽象观察者类
(4) Player:战队成员类,充当具体观察者类
(5) Client:客户端测试类
import java.util.*;
interface Observer{
public String getName();
public void setName(String name);
public void help();//声明支援盟友方法
public void baAttecked(AllyControlCenter acc);//声明遭受伤害
//战队成员类 :具体观察类
public class player implements Observer{
private String name;
public player(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
//支援盟友方法的实现
@Override
public void help() {
System.out.println("坚持住,"+this.name+"来救你!");
}
//遭到攻击通知盟友
@Override
public void baAttecked(AllyControlCenter acc) {
System.out.println(this.name+"被攻击");
acc.notifyObserver(name);
}
}
}
//战队控制中心:目标类
abstract class AllyControlCenter {
protected String allyName;
protected ArrayList<Observer> players =new ArrayList<Observer>();//定义集合
public String getAllyName() {
return allyName;
}
public void setAllyName(String allyName) {
this.allyName = allyName;
}
public void join(Observer obs) {
System.out.println(obs.getName()+"加入"+this.allyName+"战队!");
players.add(obs);
}
public void quit(Observer obs) {
System.out.println(obs.getName()+"退出"+this.allyName+"战队!");
players.remove(obs);
}
public abstract void notifyObserver(String name);
}
class ConcreteAllControlCenter extends AllyControlCenter{
public ConcreteAllControlCenter(String name ) {
System.out.println(name +"战队组建成功!");
this.allyName=name;
}
//实现通知方法
@Override
public void notifyObserver(String name) {
System.out.println(this.allyName+"战队紧急通知,盟友"+name+"遭受敌人攻击");
//遍历除了自己以外的队友
for (Object obs:players){
if (!((Observer)obs).getName().equals(name)){
((Observer) obs).help();
};
}
}
}
客户端
public class Client {
public static void main(String[] args) {
AllyControlCenter acc;
acc =new ConcreteAllControlCenter("北信F5");
Observer player1,player2,player3,player4,player5;
player1 =new Observer.player("张泽帅");
acc.join(player1);
player2 =new Observer.player("马继");
acc.join(player2);
player3 =new Observer.player("曹帆");
acc.join(player3);
player4 =new Observer.player("史梦琦");
acc.join(player4);
player1.baAttecked(acc);
}
}
结果
模式优点
可以实现表示层和数据逻辑层的分离
在观察目标和观察者之间建立一个抽象的耦合
支持广播通信,简化了一对多系统设计的难度
符合开闭原则,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便
模式缺点
将所有的观察者都通知到会花费很多时间
如果存在循环依赖时可能导致系统崩溃
没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而只是知道观察目标发生了变化
模式适用环境
一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用
一个对象的改变将导致一个或多个其他对象发生改变,且并不知道具体有多少对象将发生改变,也不知道这些对象是谁
需要在系统中创建一个触发链
中介者模式
分析
QQ聊天的两种方式:
(1) 用户与用户直接聊天,用户与用户之间存在多对多的联系,这将导致系统中用户之间的关系非常复杂,一个用户如果要将相同的信息或文件发送给其他所有用户,必须一个一个地发送
(2) 通过QQ群聊天,用户只需要将信息或文件发送到群中或上传为群共享文件即可,群的作用就是将发送者所发送的信息和文件转发给每一个接收者,将极大地减少系统中用户之间的两两通信
软件开发:
网状结构:多对多联系将导致系统非常复杂,几乎每个对象都需要与其他对象发生相互作用,而这种相互作用表现为一个对象与另外一个对象的直接耦合,这将导致一个过度耦合的系统
星型结构:中介者模式将系统的网状结构变成以中介者为中心的星型结构,同事对象不再直接与另一个对象联系,它通过中介者对象与另一个对象发生相互作用。系统的结构不会因为新对象的引入带来大量的修改工作
定义
中介者模式:定义一个对象来封装一系列对象的交互。中介者模式使各对象之间不需要显式地相互引用,从而使其耦合松散,而且让你可以独立地改变它们之间的交互
又称为调停者模式
在中介者模式中,通过引入中介者来简化对象之间的复杂交互
中介者模式是迪米特法则的一个典型应用
对象之间多对多的复杂关系转化为相对简单的一对多关系
结构
中介者模式包含以下4个角色:
Mediator(抽象中介者)
ConcreteMediator(具体中介者)
Colleague(抽象同事类)
ConcreteColleague(具体同事类)
中介者类的职责
中转作用(结构性):各个同事对象不再需要显式地引用其他同事,当需要和其他同事进行通信时,可通过中介者来实现间接调用
协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装
代码
典型的抽象中介者类代码:
public abstract class Mediator {
protected ArrayList<Colleague> colleagues = new ArrayList<Colleague>(); //用于存储同事对象
//注册方法,用于增加同事对象
public void register(Colleague colleague) {
colleagues.add(colleague);
}
//声明抽象的业务方法
public abstract void operation();
}
典型的具体中介者类代码:
public class ConcreteMediator extends Mediator {
//实现业务方法,封装同事之间的调用
public void operation() {
......
((Colleague)(colleagues.get(0))).method1(); //通过中介者调用同事类的方法
......
}
}
典型的抽象同事类代码
public abstract class Colleague {
protected Mediator mediator; //维持一个抽象中介者的引用
public Colleague(Mediator mediator) {
this.mediator=mediator;
}
public abstract void method1(); //声明自身方法,处理自己的行为
//定义依赖方法,与中介者进行通信
public void method2() {
mediator.operation();
}
}
典型的具体同事类代码:
public class ConcreteColleague extends Colleague {
public ConcreteColleague(Mediator mediator) {
super(mediator);
}
//实现自身方法
public void method1() {
......
}
}
应用实例
某软件公司要开发一套CRM系统,其中包含一个客户信息管理模块,所设计的“客户信息管理窗口”界面效果图如下图所示:
“客户信息管理窗口”界面效果图
通过分析发现,在上图中,界面组件之间存在较为复杂的交互关系:如果删除一个客户,将从客户列表(List)中删掉对应的项,客户选择组合框(ComboBox)中的客户名称也将减少一个;如果增加一个客户信息,则客户列表中将增加一个客户,且组合框中也将增加一项。
为了更好地处理界面组件之间的交互,现使用中介者模式设计该系统。
结构类图
实例代码
(1) Mediator:抽象中介者类
(2) ConcreteMediator:具体中介者类
(3) Component:抽象组件类,充当抽象同事类
(4) Button:按钮类,充当具体同事类
(5) List:列表框类,充当具体同事类
(6) ComboBox:组合框类,充当具体同事类
(7) TextBox:文本框类,充当具体同事类
(8) Client:客户端测试类
//抽象中介者
abstract class Mediator {
public abstract void componentChanged(Component c);
}
class ConcreteMediator extends Mediator {
public Button addButton;
public List list;
public TextBox userNameTextBox;
public ComboBox cb;
@Override
public void componentChanged(Component c) {
//单击按钮
if (c==addButton){
System.out.println("--单击增加按钮");
list.update();
cb.update();
userNameTextBox.update();
}
//从列表框选择客户
else if(c==list){
System.out.println("--从列表框选择客户--");
cb.select();
userNameTextBox.selText();
}
//从组合框选着客户
else if(c==cb){
System.out.println("--从列表框选择客户--");
cb.select();
userNameTextBox.selText();
}
}
}
//抽象组件类:抽象同事类
abstract class Component {
protected Mediator mediator;
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
//转发调用
public void changed(){
mediator.componentChanged(this);
}
public abstract void update();
}
//按钮类:具体同事类
class Button extends Component{
@Override
public void update() {
//按钮不产生响应
}
}
//列表款类:具体同事类
class List extends Component{
@Override
public void update() {
System.out.println("列表框增加一项:张无忌");
}
public void select(){
System.out.println("列表框选中项:小龙女");
}
}
//文本框类:具体同事类
class TextBox extends Component {
@Override
public void update() {
System.out.println("客户端信息增加成功后文本款清除");
}
public void selText() {
System.out.println("文本框显示:小龙女");
}
}
//组合框类:集体同事类
class ComboBox extends Component {
@Override
public void update() {
System.out.println("组合框增加一项:张无忌");
}
public void select(){
System.out.println("组合框选中项:小龙女");
}
}
//客户端代码
class Client{
public static void main(String[] args) {
//定义中介者对象
ConcreteMediator mediator;
mediator=new ConcreteMediator();
//定义同事对象
Button addBT=new Button();
List list=new List();
ComboBox cb=new ComboBox();
TextBox userNameTB=new TextBox();
addBT.setMediator(mediator);
list.setMediator(mediator);
cb.setMediator(mediator);
userNameTB.setMediator(mediator);
mediator.addButton=addBT;
mediator.list=list;
mediator.cb=cb;
mediator.userNameTextBox=userNameTB;
addBT.changed();
System.out.println("-----------------------------");
list.changed();
}
}
结果
模式优点
简化了对象之间的交互,它用中介者和同事的一对多交互代替了原来同事之间的多对多交互,将原本难以理解的网状结构转换成相对简单的星型结构
可将各同事对象解耦
可以减少子类生成,中介者模式将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,这使得各个同事类可被重用,无须直接对同事类进行扩展
模式缺点
在具体中介者类中包含了大量的同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护
模式适用环境
系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解
一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象
想通过一个中间类来封装多个类中的行为,又不想生成太多的子类
备忘录模式
分析
备忘录模式——软件中的“后悔药”——撤销(Undo)
通过使用备忘录模式可以让系统恢复到某一特定的历史状态
首先保存软件系统的历史状态,当用户需要取消错误操作并且返回到某个历史状态时,可以取出事先保存的历史状态来覆盖当前状态
定义
备忘录模式:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样就可以在以后将对象恢复到原先保存的状态。
备忘录模式:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样就可以在以后将对象恢复到原先保存的状态。
结构
备忘录模式包含以下3个角色:
Originator(原发器)
Memento(备忘录)
Caretaker(负责人)
代码
典型的原发器类代码
package designpatterns.memento;
public class Originator {
private String state;
public Originator(){}
//创建一个备忘录对象
public Memento createMemento() {
return new Memento(this);
}
//根据备忘录对象恢复原发器状态
public void restoreMemento(Memento m) {
state = m.state;
}
public void setState(String state) {
this.state=state;
}
public String getState() {
return this.state;
}
}
典型的备忘录类代码:
package designpatterns.memento;
//备忘录类,默认可见性,包内可见
class Memento {
private String state;
Memento(Originator o) {
state = o.getState();
}
void setState(String state) {
this.state=state;
}
String getState() {
return this.state;
}
}
备忘录模式的实现
除了Originator类,不允许其他类来调用备忘录类Memento的构造函数与相关方法
如果允许其他类调用setState()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义
理想的情况是只允许生成该备忘录的原发器访问备忘录的内部状态
Java语言实现:
将Memento类与Originator类定义在同一个包(package)中来实现封装,使用默认可见性定义Memento类,即保证其在包内可见
将备忘录类作为原发器类的内部类,使得只有原发器才可以访问备忘录中的数据,其他对象都无法使用备忘录中的数据
典型的负责人类代码:
package designpatterns.memento;
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento=memento;
}
}
应用实例
某软件公司要使用Java语言开发一款可以运行在Android平台的触摸式中国象棋软件,由于考虑到有些用户是“菜鸟”,经常不小心走错棋;还有些用户因为不习惯使用手指在手机屏幕上拖动棋子,常常出现操作失误,因此该中国象棋软件要提供“悔棋”功能,在用户走错棋或操作失误后可恢复到前一个步骤。如下图所示:
中国象棋软件界面示意图
为了实现“悔棋”功能,现使用备忘录模式来设计该中国象棋软件。
实例类图
实例代码
(1) Chessman:象棋棋子类,充当原发器
(2) ChessmanMemento:象棋棋子备忘录类,充当备忘录
(3) MementoCaretaker:象棋棋子备忘录管理类,充当负责人
(4) Client:客户端测试类
public class Chessman {
private String lanel;
private int x;
private int y;
public Chessman(String lanel, int x, int y) {
this.lanel = lanel;
this.x = x;
this.y = y;
}
public String getLanel() {
return lanel;
}
public void setLanel(String lanel) {
this.lanel = lanel;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
//保存状态
public ChessmanMemento Save(){
return new ChessmanMemento(lanel,x,y);
}
//恢复状态
public void restore(ChessmanMemento memento){
lanel=memento.getLanel();
x=memento.getX();
y=memento.getY();
}
}
//象棋棋子备忘类:备忘录
class ChessmanMemento {
private String lanel;
private int x;
private int y;
public ChessmanMemento(String lanel, int x, int y) {
this.lanel = lanel;
this.x = x;
this.y = y;
}
public String getLanel() {
return lanel;
}
public void setLanel(String lanel) {
this.lanel = lanel;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
class MementoCaretaker{
private ChessmanMemento memento;
public ChessmanMemento getMemento() {
return memento;
}
public void setMemento(ChessmanMemento memento) {
this.memento = memento;
}
}
class client{
public static void main(String[] args) {
MementoCaretaker mementoCaretaker=new MementoCaretaker();
Chessman chessman=new Chessman("车",1,1);
display(chessman);
mementoCaretaker.setMemento(chessman.Save());//保存状态
chessman.setX(5);
display(chessman);
System.out.println("------悔棋------");
chessman.restore(mementoCaretaker.getMemento());//恢复状态
display(chessman);
}
private static void display(Chessman chessman) {
System.out.println("棋子"+chessman.getLanel()+"当前位置:"+chessman.getX()+"行"+chessman.getY()+"列");
}
}
结果
动机
有时候用户需要撤销多步操作
实现方案:在负责人类中定义一个集合来存储多个备忘录,每个备忘录负责保存一个历史状态,在撤销时可以对备忘录集合进行逆向遍历,回到一个指定的历史状态,还可以对备忘录集合进行正向遍历,实现重做(Redo)或恢复操作,即取消撤销,让对象状态得到恢复
实现
import java.util.*;
class MementoCaretaker {
//定义一个集合来存储多个备忘录
private ArrayList<ChessmanMemento> mementolist = new ArrayList <ChessmanMemento>();
public ChessmanMemento getMemento(int i) {
return (ChessmanMemento)mementolist.get(mementolist.size()-i);//恢复几步前的事
}
public void setMemento(ChessmanMemento memento) {
mementolist.add(memento);
}
}
class client{
public static void main(String[] args) {
MementoCaretaker mementoCaretaker=new MementoCaretaker();
Chessman chessman=new Chessman("车",1,1);
display(chessman);
mementoCaretaker.setMemento(chessman.Save());//保存状态
chessman.setX(5);
display(chessman);
System.out.println("------悔棋------");
chessman.restore(mementoCaretaker.getMemento(1));//恢复状态
display(chessman);
}
private static void display(Chessman chessman) {
System.out.println("棋子"+chessman.getLanel()+"当前位置:"+chessman.getX()+"行"+chessman.getY()+"列");
}
}
结果
模式优点
提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤
实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动
模式缺点
资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免地需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源
模式适用环境
保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时能够恢复到先前的状态,实现撤销操作
防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象
状态模式
分析
在软件系统中:
有些对象具有多种状态
这些状态在某些情况下能够相互转换
对象在不同的状态下将具有不同的行为
复杂的条件判断语句来进行状态的判断和转换操作
导致代码的可维护性和灵活性下降
出现新的状态时,代码的扩展性很差,客户端代码也需要进行相应的修改,违背了开闭原则
定义
状态模式:允许一个对象在其内部状态改变时改变它的行为。让对象看起来似乎修改了它的类。
又名状态对象(Objects for States)
用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题
将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化
对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理
结构
状态模式包含以下3个角色:
Context(环境类)
State(抽象状态类)
ConcreteState(具体状态类)
代码
典型的抽象状态类代码:
public abstract class State {
//声明抽象业务方法,不同的具体状态类可以有不同的实现
public abstract void handle();
}
典型的具体状态类代码:
public class ConcreteState extends State {
public void handle() {
//方法具体实现代码
}
}
典型的环境类代码:
public class Context {
private State state; //维持一个对抽象状态对象的引用
private int value; //其他属性值,该属性值的变化可能会导致对象的状态发生变化
public void setState(State state) {
this.state = state;
}
public void request() {
//其他代码
state.handle(); //调用状态对象的业务方法
//其他代码
}
}
状态转换的实现
(1) 统一由环境类来负责状态之间的转换,环境类充当了状态管理器(State Manager)角色
……
public void changeState()
{
//判断属性值,根据属性值进行状态转换
if (value == 0)
{
this.setState(new ConcreteStateA());
}
else if (value == 1)
{
this.setState(new ConcreteStateB());
}
......
}
……
……
public void changeState(Context ctx) {
//根据环境对象中的属性值进行状态转换
if (ctx.getValue() == 1) {
ctx.setState(new ConcreteStateB());
}
else if (ctx.getValue() == 2) {
ctx.setState(new ConcreteStateC());
}
......
}
……
应用实例
某软件公司要为一银行开发一套信用卡业务系统,银行账户(Account)是该系统的核心类之一,通过分析,该软件公司开发人员发现在系统中账户存在3种状态,且在不同状态下账户存在不同的行为,具体说明如下:
(1) 如果账户中余额大于等于0,则账户的状态为正常状态(Normal State),此时用户既可以向该账户存款也可以从该账户取款;
(2) 如果账户中余额小于0,并且大于-2000,则账户的状态为透支状态(Overdraft State),此时用户既可以向该账户存款也可以从该账户取款,但需要按天计算利息;
(3) 如果账户中余额等于-2000,那么账户的状态为受限状态(Restricted State),此时用户只能向该账户存款,不能再从中取款,同时也将按天计算利息;
(4) 根据余额的不同,以上3种状态可发生相互转换。
现使用状态模式设计并实现银行账户状态的转换。
实例分析与类图
实例代码
(1) Account:银行账户,充当环境类
(2) AccountState:账户状态类,充当抽象状态类
(3) NormalState:正常状态类,充当具体状态类
(4) OverdraftState:透支状态类,充当具体状态类
(5) RestrictedState:受限状态类,充当具体状态类
(6) Client:客户端测试类
package 状态模式;
public class Account {
private AccountState state;//维持一个对抽象对象的引用
private double balance = 0;//账户余额
private String Owner;//开户名
public Account(String owner, double balance) {
this.balance = balance;
Owner = owner;
this.state = new NormalState(this);//设置初始状态
System.out.println(this.Owner + "开户,初始金额为:" + balance);
System.out.println("------------------------------");
}
public void setState(AccountState state) {
this.state = state;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public void deposit(double amount) {
System.out.println(this.Owner + "存款" + amount);
state.deposit(amount);//调用状态对象的deposit方法
System.out.println("现在余额为" + this.balance);
System.out.println("现在账户状态为" + this.state.getClass().getName());
System.out.println("------------------------..");
}
public void withdraw(double amount) {
System.out.println(this.Owner + "取款" + amount);
state.deposit(amount);//调用状态对象的deposit方法
System.out.println("现在余额为" + this.balance);
System.out.println("现在账户状态为" + this.state.getClass().getName());
System.out.println("------------------------..");
}
public void computeInterest() {
state.computeInterest();
}
}
abstract class AccountState {
public Account acc;
public abstract void deposit(double amount);
public abstract void withdraw(double amount);
public abstract void computeInterest();
public abstract void stateCheck();
}
class NormalState extends AccountState {
public NormalState(Account acc) {
this.acc = acc;
}
public NormalState(AccountState state) {
this.acc = state.acc;
}
@Override
public void deposit(double amount) {
acc.setBalance(acc.getBalance() + amount);
stateCheck();
}
@Override
public void withdraw(double amount) {
acc.setBalance(acc.getBalance() - amount);
stateCheck();
}
@Override
public void computeInterest() {
System.out.println("状态正常,无需支付利息!");
}
@Override
public void stateCheck() {
if (acc.getBalance() > -2000 && acc.getBalance() <= 0) {
acc.setState(new NormalState(this));
} else if (acc.getBalance() == -2000) {
acc.setState(new RestrictedState(this));
} else if (acc.getBalance() < -2000) {
System.out.println("操作受限!");
}
}
}
class OverdraftState extends AccountState {
public OverdraftState(AccountState state) {
this.acc = state.acc;
}
@Override
public void deposit(double amount) {
acc.setBalance(acc.getBalance() + amount);
stateCheck();
}
@Override
public void withdraw(double amount) {
acc.setBalance(acc.getBalance() - amount);
stateCheck();
}
@Override
public void computeInterest() {
System.out.println("计算利息!");
}
@Override
public void stateCheck() {
if (acc.getBalance() > -2000 && acc.getBalance() <= 0) {
acc.setState(new NormalState(this));
} else if (acc.getBalance() == -2000) {
acc.setState(new RestrictedState(this));
} else if (acc.getBalance() < -2000) {
System.out.println("操作受限!");
}
}
}
class RestrictedState extends AccountState {
public RestrictedState(AccountState state) {
this.acc = state.acc;
}
@Override
public void deposit(double amount) {
acc.setBalance(acc.getBalance() + amount);
stateCheck();
}
@Override
public void withdraw(double amount) {
System.out.println("账号受限,取款失败");
}
@Override
public void computeInterest() {
System.out.println("计算利息!");
}
@Override
public void stateCheck() {
if (acc.getBalance() > -2000 && acc.getBalance() <= 0) {
acc.setState(new NormalState(this));
} else if (acc.getBalance() == -2000) {
acc.setState(new RestrictedState(this));
} else if (acc.getBalance() < -2000) {
System.out.println("操作受限!");
}
}
}
//客户端代码
class Client{
public static void main(String[] args) {
Account account=new Account("段誉",0.0);
account.deposit(1000);
account.withdraw(2000);
account.deposit(3000);
account.withdraw(4000);
account.withdraw(1000);
account.computeInterest();
}
}
结果
模式优点
封装了状态的转换规则,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中
将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为
允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起
可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
模式缺点
会增加系统中类和对象的个数,导致系统运行开销增大
结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱,增加系统设计的难度
对开闭原则的支持并不太好,增加新的状态类需要修改负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需要修改对应类的源代码
模式适用环境
对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化
在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强
策略模式
分析
实现某个目标的途径不止一条,可根据实际情况选择一条合适的途径
软件开发:
多种算法,例如排序、查找、打折等
使用硬编码(Hard Coding)实现将导致系统违背开闭原则,扩展性差,且维护困难
可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法
定义
策略模式:定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法可以独立于使用它的客户变化。
又称为政策(Policy)模式
每一个封装算法的类称之为策略(Strategy)类
策略模式提供了一种可插入式(Pluggable)算法的实现方案
结构
策略模式包含以下3个角色:
Context(环境类)
Strategy(抽象策略类)
ConcreteStrategy(具体策略类)
代码
典型的抽象策略类代码:
public abstract class Strategy {
public abstract void algorithm(); //声明抽象算法
}
典型的具体策略类代码:
public class ConcreteStrategyA extends Strategy {
//算法的具体实现
public void algorithm() {
//算法A
}
}
策略模式的实现
public class Context {
private Strategy strategy; //维持一个对抽象策略类的引用
//注入策略对象
public void setStrategy(Strategy strategy) {
this.strategy= strategy;
}
//调用策略类中的算法
public void algorithm() {
strategy.algorithm();
}
}
典型的客户端代码片段:
……
Context context = new Context();
Strategy strategy;
strategy = new ConcreteStrategyA(); //可在运行时指定类型,通过配置文件和反射机制实现
context.setStrategy(strategy);
context.algorithm();
……
实例说明
某软件公司为某电影院开发了一套影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票打折方式,具体打折方案如下:
(1) 学生凭学生证可享受票价8折优惠。
(2) 年龄在10周岁及以下的儿童可享受每张票减免10元的优惠(原始票价需大于等于20元)。
(3) 影院VIP用户除享受票价半价优惠外还可进行积分,积分累计到一定额度可换取电影院赠送的奖品。
该系统在将来可能还要根据需要引入新的打折方式。现使用策略模式设计该影院售票系统的打折方案。
实例类图
实例代码
(1) MovieTicket:电影票类,充当环境类
(2) Discount:折扣类,充当抽象策略类
(3) StudentDiscount:学生票折扣类,充当具体策略类
(4) ChildrenDiscount:儿童票折扣类,充当具体策略类
(5) VIPDiscount:VIP会员票折扣类,充当具体策略类
(6) Client:客户端测试类
代码
package 策略模式;
public class MovieTicket {
private double price;
private Discount discount;//维护一个对抽象折扣类的引用
public void setPrice(double price) {
this.price = price;
}
public void setDiscount(Discount discount) {
this.discount = discount;
}
public double getPrice() {
//调用折扣类的折扣价计算方式
return discount.calculate(this.price);
}
}
// 折扣类 :抽象策略类
interface Discount {
public double calculate(double peice);
}
//学生票折扣类:具体策略类
class StudentDiscount implements Discount{
@Override
public double calculate(double peice) {
System.out.println("学生票:");
return peice*0.8;
}
}
//儿童票折扣类:具体策略类
class ChildrenDiscount implements Discount{
@Override
public double calculate(double peice) {
System.out.println("儿童票:");
return peice -10;
}
}
//学生票折扣类:具体策略类
class VIPDiscount implements Discount{
@Override
public double calculate(double peice) {
System.out.println("VIP票:");
System.out.println("增加积分:");
return peice * 0.5;
}
}
xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<className>策略模式.StudentDiscount</className>
</configuration>
读取类
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class XMLUtil {
//该方法用于从XML文件中提取具体类类名,并返回一个实例对象,可以通过参数的不同返回不同的类
//同类名节点所对应的实例
public static Object getBean(){
try {
//创建文档对象
DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=dFactory.newDocumentBuilder();
Document doc;
doc=builder.parse(new File("src/策略模式/config.xml"));
//获取包含类名的文本节点
NodeList nl=doc.getElementsByTagName("className");
Node classNode =nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//同类名生成实例对象并将其返回、
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
客户端
package 策略模式;
public class Client {
public static void main(String[] args) {
MovieTicket mt=new MovieTicket();
double originalPrice=60.0;
double currentPrice;
mt.setPrice(originalPrice);
System.out.println("原始价:"+originalPrice);
System.out.println("-------------------");
Discount discount;
discount =(Discount) XMLUtil.getBean();//读取配置文件
mt.setDiscount(discount);//注入对象
currentPrice=mt.getPrice();
System.out.println("折后价位:"+currentPrice);
}
}
结果
模式优点
提供了对开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为
提供了管理相关的算法族的办法
提供了一种可以替换继承关系的办法
可以避免多重条件选择语句
提供了一种算法的复用机制,不同的环境类可以方便地复用策略类
模式缺点
客户端必须知道所有的策略类,并自行决定使用哪一个策略类
将造成系统产生很多具体策略类
无法同时在客户端使用多个策略类
模式适用环境
一个系统需要动态地在几种算法中选择一种
避免使用难以维护的多重条件选择语句
不希望客户端知道复杂的、与算法相关的数据结构,提高算法的保密性与安全性
模板方法模式
定义
模板方法模式:定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类不改变一个算法的结构即可重定义该算法的某些特定步骤。
是一种基于继承的代码复用技术
将一些复杂流程的实现步骤封装在一系列基本方法中
在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果
结构
模板方法模式包含以下两个角色:
AbstractClass(抽象类)
ConcreteClass(具体子类)
模板方法 (Template Method)
基本方法 (Primitive Method)
抽象方法(Abstract Method)
具体方法(Concrete Method)
钩子方法(Hook Method)
代码
抽象类典型代码:
public abstract class AbstractClass {
//模板方法
public void templateMethod() {
primitiveOperation1();
primitiveOperation2();
primitiveOperation3();
}
//基本方法—具体方法
public void primitiveOperation1() {
//实现代码
}
//基本方法—抽象方法
public abstract void primitiveOperation2();
//基本方法—钩子方法
public void primitiveOperation3()
{ }
}
具体子类典型代码
public class ConcreteClass extends AbstractClass {
public void primitiveOperation2() {
//实现代码
}
public void primitiveOperation3() {
//实现代码
}
}
应用实例
某软件公司要为某银行的业务支撑系统开发一个利息计算模块,利息的计算流程如下:
(1) 系统根据账号和密码验证用户信息,如果用户信息错误,则系统显示出错提示。
(2) 如果用户信息正确,则根据用户类型的不同使用不同的利息计算公式计算利息(如活期账户和定期账户具有不同的利息计算公式)。
(3) 系统显示利息。
现使用模板方法模式设计该利息计算模块。
实例代码
(1) Account:账户类,充当抽象类
(2) CurrentAccount:活期账户类,充当具体子类
(3) SavingAccount:定期账户类,充当具体子类
(4) Client:客户端测试类
abstract class Account {
//基本方法--具体方法
public boolean validate(String account,String password){
System.out.println("账号:"+account);
System.out.println("密码"+password);
if (account.equals("张无忌")&&password.equals("123456")){
return true;
}else {
return false;
}
}
//基本方法--抽象方法
public abstract void calculateInterest();
//基本方法--具体方法
public void dispaly(){
System.out.println("显示利息!");
}
//模板方法
public void handle(String account,String password){
if (!validate(account,password)){
System.out.println("账号密码错误");
return ;
}
calculateInterest();
dispaly();
}
}
//活期账户类
class CurrentAccount extends Account{
@Override
public void calculateInterest() {
System.out.println("按活期利率计算利息!");
}
}
//定期账户类
class SavingAccount extends Account{
@Override
public void calculateInterest() {
System.out.println("按定期利率计算利息!");
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<className>模板方法模式.CurrentAccount</className>
</configuration>
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class XMLUtil {
//该方法用于从XML文件中提取具体类类名,并返回一个实例对象,可以通过参数的不同返回不同的类
//同类名节点所对应的实例
public static Object getBean(){
try {
//创建文档对象
DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=dFactory.newDocumentBuilder();
Document doc;
doc=builder.parse(new File("src/模板方法模式/config.xml"));
//获取包含类名的文本节点
NodeList nl=doc.getElementsByTagName("className");
Node classNode =nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//同类名生成实例对象并将其返回、
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
public class Client {
public static void main(String[] args) {
Account account;
account =(Account) XMLUtil.getBean();
account.handle("张无忌","123456");
}
}
结果
解释器模式
分析
Java语言无法直接解释类似“1 + 2 + 3 – 4 + 1”这样的字符串
定义一套文法规则来实现对这些语句的解释,即设计一个自定义语言
基于现有的编程语言 -> 面向对象编程语言 --> 解释器模式
定义
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
在解释器模式的定义中所指的“语言”是使用规定格式和语法的代码
是一种使用频率相对较低但学习难度相对较大的设计模式,用于描述如何使用面向对象语言构成一个简单的语言解释器
能够加深对面向对象思想的理解,并且理解编程语言中文法规则的解释过程
结构
解释器模式包含以下4个角色:
AbstractExpression(抽象表达式)
TerminalExpression(终结符表达式)
NonterminalExpression(非终结符表达式)
Context(环境类)
代码
典型的抽象表达式类代码:
public abstract class AbstractExpression {
public abstract void interpret(Context ctx);
}
典型的终结符表达式类代码:
public class TerminalExpression extends AbstractExpression {
public void interpret(Context ctx) {
//终结符表达式的解释操作
}
}
典型的非终结符表达式类代码:
public class NonterminalExpression extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
public NonterminalExpression(AbstractExpression left,AbstractExpression right) {
this.left=left;
this.right=right;
}
public void interpret(Context ctx) {
//递归调用每一个组成部分的interpret()方法
//在递归调用时指定组成部分的连接方式,即非终结符的功能
}
}
环境类Context:
用于存储一些全局信息,一般包含一个HashMap或ArrayList等类型的集合对象(也可以直接由HashMap等集合类充当环境类),存储一系列公共信息,例如变量名与值的映射关系(key/value)等,用于在执行具体的解释操作时从中获取相关信息
可以在环境类中增加一些所有表达式解释器都共有的功能,以减轻解释器的职责
当系统无须提供全局公共信息时可以省略环境类,根据实际情况决定是否需要环境类
public class Context {
private HashMap<String, String> map = new HashMap<String, String>();
public void assign(String key, String value) {
//往环境类中设值
map.put(key, value);
}
public String lookup(String key) {
//获取存储在环境类中的值
return map.get(key);
}
}
应用实例
某软件公司要开发一套机器人控制程序,在该机器人控制程序中包含一些简单的英文控制指令,每一个指令对应一个表达式(expression),该表达式可以是简单表达式也可以是复合表达式。每一个简单表达式由移动方向(direction),移动方式(action)和移动距离(distance)三部分组成,其中,移动方向包括向上(up)、向下(down)、向左(left)、向右(right);移动方式包括移动(move)和快速移动(run);移动距离为一个正整数。两个表达式之间可以通过与(and)连接,形成复合(composite)表达式。
用户通过对图形化的设置界面进行操作可以创建一个机器人控制指令,机器人在收到指令后将按照指令的设置进行移动,例如输入控制指令“up move 5”将“向上移动5个单位”;输入控制指令“down run 10 and left move 20”将“向下快速移动10个单位再向左移动20个单位”。
现使用解释器模式来设计该程序并模拟实现。
实例分析及类图
文法规则
expression ::= direction action distance | composite //表达式
composite ::= expression ‘and’ expression //复合表达式
direction ::= ‘up’ | ‘down’ | ‘left’ | ‘right’ //移动方向
action ::= ‘move’ | ‘run’ //移动方式
distance ::= an integer //移动距离
终结符表达式direction、action和distance对应DirectionNode类、ActionNode类和DistanceNode类
非终结符表达式expression和composite对应SentenceNode类和AndNode类
(1) AbstractNode:抽象结点类,充当抽象表达式角色
(2) AndNode:And结点类,充当非终结符表达式角色
(3) SentenceNode:简单句子结点类,充当非终结符表达式角色
(4) DirectionNode:方向结点类,充当终结符表达式角色
(5) ActionNode:动作结点类,充当终结符表达式角色
(6) DistanceNode:距离结点类,充当终结符表达式角色
(7) InstructionHandler:指令处理类,工具类
(8) Client:客户端测试类
package 解释器模式;
import java.util.Stack;
/*
* 本实例对机器人的控制指令的输出结果进行模拟,将英文指令翻译为中文指令实际情况是调用
* 不同的控制程序对机器人进行控制,包括对移动方向的,方式和距离的控制
* */
//抽象表达式
abstract class AbstractNode {
public abstract String interpeet();
}
//and解释 :非终结符表达式
class AndNode extends AbstractNode {
private AbstractNode lefr;
private AbstractNode right;
public AndNode(AbstractNode lefr, AbstractNode right) {
this.lefr = lefr;
this.right = right;
}
@Override
public String interpeet() {
return lefr.interpeet() + "再" + right.interpeet();
}
}
//简单句子解释 :非终结符表达式
class SentenceNode extends AbstractNode {
private AbstractNode direction;
private AbstractNode action;
private AbstractNode distance;
public SentenceNode(AbstractNode direction, AbstractNode action, AbstractNode distance) {
this.direction = direction;
this.action = action;
this.distance = distance;
}
@Override
public String interpeet() {
return direction.interpeet() + action.interpeet() + distance.interpeet();
}
}
//方向 解释:终结符表达式
class DirectionNode extends AbstractNode {
private String direction;
public DirectionNode(String direction) {
this.direction = direction;
}
@Override
public String interpeet() {
if (direction.equalsIgnoreCase("up")) {
return "向上";
} else if (direction.equalsIgnoreCase("down")) {
return "向下";
} else if (direction.equalsIgnoreCase("left")) {
return "向左";
} else if (direction.equalsIgnoreCase("right")) {
return "向右";
} else {
return "无效指令";
}
}
}
//动作解释:终结符表达式
class ActionNode extends AbstractNode {
private String action;
public ActionNode(String action) {
this.action = action;
}
@Override
public String interpeet() {
if (action.equalsIgnoreCase("move")) {
return "移动";
} else if (action.equalsIgnoreCase("run")) {
return "快速移动";
} else {
return "无效指令";
}
}
}
//距离解释:终结符表达式
class DistanceNode extends AbstractNode {
private String distance;
public DistanceNode(String distance) {
this.distance = distance;
}
@Override
public String interpeet() {
return this.distance;
}
}
//指令处理类 :工具类
class InstrutionHandler {
private AbstractNode node;
public void handle(String instruction) {
AbstractNode left = null, right = null;
AbstractNode direction = null, action = null, distance = null;
Stack stack = new Stack();//声明一个栈对象用于存储抽象法树
String[] words = instruction.split(" ");//空格分割指令字符串
for (int i = 0; i < words.length; i++) {
if (words[i].equalsIgnoreCase("and")) {
left = (AbstractNode) stack.pop();
String word1 = words[++i];
direction = new DirectionNode(word1);
String word2 = words[++i];
action = new ActionNode(word2);
String word3 = words[++i];
distance = new DistanceNode(word3);
right = new SentenceNode(direction, action, distance);
stack.push(new AndNode(left, right));
} else {
String word1 = words[i];
direction = new DirectionNode(word1);
String word2 = words[++i];
action = new ActionNode(word2);
String word3 = words[++i];
distance = new DistanceNode(word3);
left = new SentenceNode(direction, action, distance);
stack.push(left);
}
}
this.node = (AbstractNode) stack.pop();//将全部表达式从栈中弹出
}
public String output() {
String result = node.interpeet();
return result;
}
}
客户端
public class Client {
public static void main(String[] args) {
String instrution="up move 5 and down run 10 and left move 5";
InstrutionHandler handler=new InstrutionHandler();
handler.handle(instrution);
String outString;
outString=handler.output();
System.out.println(outString);
}
}
结果
模式优点
易于改变和扩展文法
可以方便地实现一个简单的语言
实现文法较为容易(有自动生成工具)
增加新的解释表达式较为方便
模式缺点
对于复杂文法难以维护
执行效率较低
模式适用环境
可以将一个需要解释执行的语言中的句子表示为一棵抽象语法树
一些重复出现的问题可以用一种简单的语言来进行表达
一个语言的文法较为简单
执行效率不是关键问题
结构型模式
用于描述如何将类或对象按某种布局组成更大的结构,GoF中提供了7种结构型模式。
结构型模式(Structural Pattern)关注如何将现有类或对象组织在一起形成更加强大的结构
不同的结构型模式从不同的角度组合类或对象,它们在尽可能满足各种面向对象设计原则的同时为类或对象的组合提供一系列巧妙的解决方案
享元模式
分析
享元模式:通过共享技术实现相同或相似对象的重用
享元池(Flyweight Pool):存储共享实例对象的地方
内部状态(Intrinsic State):存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享(例如:字符的内容)
外部状态(Extrinsic State):随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的(例如:字符的颜色和大小)
原理
(1) 将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的
(2) 需要的时候将对象从享元池中取出,即可实现对象的复用
(3) 通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份
定义
享元模式:运用共享技术有效地支持大量细粒度对象的复用。
又称为轻量级模式
要求能够被共享的对象必须是细粒度对象
结构
享元模式包含以下4个角色:
Flyweight(抽象享元类)
ConcreteFlyweight(具体享元类)
UnsharedConcreteFlyweight(非共享具体享元类)
FlyweightFactory(享元工厂类)
代码
典型的抽象享元类代码:
public abstract class Flyweight {
public abstract void operation(String extrinsicState);
}
典型的具体享元类代码
public class ConcreteFlyweight extends Flyweight {
//内部状态intrinsicState作为成员变量,同一个享元对象其内部状态是一致的
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
//外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即使是同一个对象,在每一次调用时可以传入不同的外部状态
public void operation(String extrinsicState) {
//实现业务方法
}
}
典型的非共享具体享元类代码:
public class UnsharedConcreteFlyweight extends Flyweight {
public void operation(String extrinsicState) {
//实现业务方法
}
}
典型的享元工厂类代码:
public class FlyweightFactory {
//定义一个HashMap用于存储享元对象,实现享元池
private HashMap flyweights = new HashMap();
public Flyweight getFlyweight(String key) {
//如果对象存在,则直接从享元池获取
if (flyweights.containsKey(key)) {
return (Flyweight)flyweights.get(key);
}
//如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
else {
Flyweight fw = new ConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
应用实例
某软件公司要开发一个围棋软件,其界面效果如下图所示:
该软件公司开发人员通过对围棋软件进行分析发现,在图中,围棋棋盘中包含大量的黑子和白子,它们的形状、大小都一模一样,只是出现的位置不同而已。如果将每一个棋子都作为一个独立的对象存储在内存中,将导致该围棋软件在运行时所需内存空间较大,如何降低运行代价、提高系统性能是需要解决的一个问题。为了解决该问题,现使用享元模式来设计该围棋软件的棋子对象。
实例类图
实例代码
(1) IgoChessman:围棋棋子类,充当抽象享元类
(2) BlackIgoChessman:黑色棋子类,充当具体享元类
(3) WhiteIgoChessman:白色棋子类,充当具体享元类
(4) IgoChessmanFactory:围棋棋子工厂类,充当享元工厂类
(5) Client:客户端测试类
代码
import java.util.Hashtable;
//围棋棋子类 :抽象享元类
abstract class IgoChessman {
public abstract String GetColor();
public void Display() {
System.out.println("棋子颜色:" + this.GetColor());
}
}
//黑色棋子类 :具体享元类
class BlackIgoChessman extends IgoChessman{
@Override
public String GetColor() {
return " 黑色";
}
}
//白色棋子类 :具体享元类
class WhiteIgoChessman extends IgoChessman{
@Override
public String GetColor() {
return " 白色";
}
}
class IgoChessmanFactory {
private static IgoChessmanFactory instance = new IgoChessmanFactory();
private Hashtable ht; //使用Hashtable来存储享元对象,充当享元池
private IgoChessmanFactory() {
ht = new Hashtable();
IgoChessman black, white;
black = new BlackIgoChessman();
ht.put("b", black);
white = new WhiteIgoChessman();
ht.put("w", white);
}
//返回享元工厂类的唯一实例
public static IgoChessmanFactory GetInstance() {
return instance;
}
//通过key来获取存储在Hashtable中的享元对象
public IgoChessman GetIgoChessman(String color) {
return (IgoChessman) ht.get(color);
}
}
客户端
class Client {
public static void main(String[] args) {
IgoChessman black1, black2, black3, white1, white2;
IgoChessmanFactory factory;
//获取享元工厂对象
factory = IgoChessmanFactory.GetInstance();
//通过享元工厂获取三颗黑子
black1 = factory.GetIgoChessman("b");
black2 = factory.GetIgoChessman("b");
black3 = factory.GetIgoChessman("b");
System.out.println("判断两颗黑子是否相同:" + (black1 == black2));
//通过享元工厂获取两颗白子
white1 = factory.GetIgoChessman("w");
white2 = factory.GetIgoChessman("w");
System.out.println("判断两颗白子是否相同:" + (white1 == white2));
//显示棋子
black1.Display();
black2.Display();
black3.Display();
white1.Display();
white2.Display();
}
}
结果
单纯享元模式
所有的具体享元类都是可以共享的,不存在非共享具体享元类
复合享元模式
将一些单纯享元对象使用组合模式加以组合
如果希望为多个内部状态不同的享元对象设置相同的外部状态,可以考虑使用复合享元模式
模式优点
可以减少内存中对象的数量,使得相同或者相似的对象在内存中只保存一份,从而可以节约系统资源,提高系统性能
外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
模式缺点
使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化
为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长
模式适用环境
一个系统有大量相同或者相似的对象,造成内存的大量耗费
对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,在需要多次重复使用享元对象时才值得使用享元模式
适配器模式
定义
适配器模式:将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
别名为包装器(Wrapper)模式
定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合
结构
适配器模式的结构(类适配器)
适配器模式的结构(对象适配器)
适配器模式包含以下3个角色:
Target(目标抽象类)
Adapter(适配器类)
Adaptee(适配者类)
代码
典型的类适配器代码:
public class Adapter extends Adaptee implements Target {
public void request() {
super.specificRequest();
}
}
典型的对象适配器代码:
public class Adapter extends Target {
private Adaptee adaptee; //维持一个对适配者对象的引用
public Adapter(Adaptee adaptee) {
this.adaptee=adaptee;
}
public void request() {
adaptee.specificRequest(); //转发调用
}
}
应用实例
某公司欲开发一款儿童玩具汽车,为了更好地吸引小朋友的注意力,该玩具汽车在移动过程中伴随着灯光闪烁和声音提示。在该公司以往的产品中已经实现了控制灯光闪烁(例如警灯闪烁)和声音提示(例如警笛音效)的程序,为了重用先前的代码并且使得汽车控制软件具有更好的灵活性和扩展性,现使用适配器模式设计该玩具汽车控制软件。
实例类图
实例代码
(1) CarController:汽车控制类,充当目标抽象类
(2) PoliceSound:警笛类,充当适配者
(3) PoliceLamp:警灯类,充当适配者
(4) PoliceCarAdapter:警车适配器,充当适配器
(5) Client:客户端测试类
(6) XMLUtil:工具类
public abstract class CarController{
public void remove(){
System.out.println("玩具汽车移动!");
}
public abstract void phonate();//发出声音
public abstract void twinkle();//灯光闪烁
}
class PoliceSound{
public void alarmSound(){
System.out.println("发出警笛声音!");
}
}
class PoliceLamp{
public void alarmLamp(){
System.out.println("呈现警灯闪烁!");
}
}
class PoliceCarAdapter extends CarController{
private PoliceSound sound;//定义适配者PoliceSound对象
private PoliceLamp lamp;//定义适配者PoliceLamp对象
public PoliceCarAdapter(){
sound = new PoliceSound();
lamp = new PoliceLamp();
}
//发出警笛声音
public void phonate(){
sound.alarmSound();//调用适配者类PoliceSound的方法
}
//呈现警灯闪烁
public void twinkle(){
lamp.alarmLamp();//调用适配者类PoliceLamp的方法
}
}
config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<className>适配器模式.PoliceCarAdapter</className>
</configuration>
XMLUtil
public class XMLUtil {
//该方法用于从XML文件中提取具体类类名,并返回一个实例对象,可以通过参数的不同返回不同的类
//同类名节点所对应的实例
public static Object getBean(){
try {
//创建文档对象
DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=dFactory.newDocumentBuilder();
Document doc;
doc=builder.parse(new File("src/适配器模式/config.xml"));
//获取包含类名的文本节点
NodeList nl=doc.getElementsByTagName("className");
Node classNode =nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//同类名生成实例对象并将其返回、
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
客户端类
public class Client{
public static void main(String args[]){
CarController car;
car = (CarController)XMLUtil.getBean();
car.remove();
car.phonate();
car.twinkle();
}
}
结果
模式优点
将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构
增加了类的透明性和复用性,提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用
灵活性和扩展性非常好
类适配器模式:置换一些适配者的方法很方便
对象适配器模式:可以把多个不同的适配者适配到同一个目标,还可以适配一个适配者的子类
模式缺点
类适配器模式:(1) 一次最多只能适配一个适配者类,不能同时适配多个适配者;(2) 适配者类不能为最终类;(3) 目标抽象类只能为接口,不能为类
对象适配器模式:在适配器中置换适配者类的某些方法比较麻烦
模式适用环境
系统需要使用一些现有的类,而这些类的接口不符合系统的需要,甚至没有这些类的源代码
创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作
组合模式
定义
组合模式:组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。
又称为“部分-整体”(Part-Whole)模式
将对象组织到树形结构中,可以用来描述整体与部分的关系
结构
组合模式包含以下3个角色:
Component(抽象构件)
Leaf(叶子构件)
Composite(容器构件)
代码
抽象构件角色典型代码
public abstract class Component {
public abstract void add(Component c); //增加成员
public abstract void remove(Component c); //删除成员
public abstract Component getChild(int i); //获取成员
public abstract void operation(); //业务方法
}
叶子构件角色典型代码:
public class Leaf extends Component {
public void add(Component c) {
//异常处理或错误提示
}
public void remove(Component c) {
//异常处理或错误提示
}
public Component getChild(int i) {
//异常处理或错误提示
return null;
}
public void operation() {
//叶子构件具体业务方法的实现
}
}
容器构件角色典型代码:
public class Composite extends Component {
private ArrayList<Component> list = new ArrayList<Component>();
public void add(Component c) {
list.add(c);
}
public void remove(Component c) {
list.remove(c);
}
public Component getChild(int i) {
return (Component)list.get(i);
}
public void operation() {
//容器构件具体业务方法的实现,将递归调用成员构件的业务方法
for(Object obj:list) {
((Component)obj).operation();
}
}
}
实例说明
某软件公司欲开发一个杀毒(Antivirus)软件,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。该杀毒软件还可以根据各类文件的特点,为不同类型的文件提供不同的杀毒方式,例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。现使用组合模式来设计该杀毒软件的整体框架。
实例代码
(1) AbstractFile:抽象文件类,充当抽象构件类
(2) ImageFile:图像文件类,充当叶子构件类
(3) TextFile:文本文件类,充当叶子构件类
(4) VideoFile:视频文件类,充当叶子构件类
(5) Folder:文件夹类,充当容器构件类
(6) Client:客户端测试类
import java.util.ArrayList;
abstract class AbsrractFile {
public abstract void add(AbsrractFile file);
public abstract void remove(AbsrractFile file);
public abstract AbsrractFile getChild(int i);
public abstract void killVirus();
}
//图像文件类 叶子构件
class ImageFile extends AbsrractFile{
private String name;
public ImageFile(String name) {
this.name = name;
}
@Override
public void add(AbsrractFile file) {
System.out.println("对不起,不支持该方法");
}
@Override
public void remove(AbsrractFile file) {
System.out.println("对不起,不支持该方法");
}
@Override
public AbsrractFile getChild(int i) {
System.out.println("对不起,不支持该方法");
return null;
}
@Override
public void killVirus() {
//模拟杀毒
System.out.println("----对图像文件,"+name+"进行杀毒");
}
}
//文本文件类 叶子构件
class TextFile extends AbsrractFile{
private String name;
public TextFile(String name) {
this.name = name;
}
@Override
public void add(AbsrractFile file) {
System.out.println("对不起,不支持该方法");
}
@Override
public void remove(AbsrractFile file) {
System.out.println("对不起,不支持该方法");
}
@Override
public AbsrractFile getChild(int i) {
System.out.println("对不起,不支持该方法");
return null;
}
@Override
public void killVirus() {
//模拟杀毒
System.out.println("----对文本文件,"+name+"进行杀毒");
}
}
//视频文件类 叶子构件
class VideoFile extends AbsrractFile{
private String name;
public VideoFile(String name) {
this.name = name;
}
@Override
public void add(AbsrractFile file) {
System.out.println("对不起,不支持该方法");
}
@Override
public void remove(AbsrractFile file) {
System.out.println("对不起,不支持该方法");
}
@Override
public AbsrractFile getChild(int i) {
System.out.println("对不起,不支持该方法");
return null;
}
@Override
public void killVirus() {
//模拟杀毒
System.out.println("----对视频文件,"+name+"进行杀毒");
}
}
//文件夹类 :容器构件
class Folder extends AbsrractFile{
//定义集合fileList ,用于存储AbsrractFile类型的成员
private ArrayList<AbsrractFile> fileList=new ArrayList<AbsrractFile>();
private String name;
public Folder(String name) {
this.name = name;
}
@Override
public void add(AbsrractFile file) {
fileList.add(file);
}
@Override
public void remove(AbsrractFile file) {
fileList.remove(file);
}
@Override
public AbsrractFile getChild(int i) {
return (AbsrractFile) fileList.get(i);
}
@Override
public void killVirus() {
System.out.println("对文件夹"+name+"进行杀毒");
for (Object obj: fileList) {
((AbsrractFile)obj).killVirus();
}
}
}
客户端
public class Client {
public static void main(String[] args) {
AbsrractFile file1,file2,file3,file4,file5,folder1,folder2,folder3,folder4;
folder1=new Folder("Sunny的资料");
folder2=new Folder("图像文件");
folder3=new Folder("文本文件");
folder4=new Folder("视频文件");
file1=new ImageFile("小龙女.jpg");
file2=new ImageFile("张无忌.gif");
file3=new TextFile("九阴真经.txt");
file4=new TextFile("葵花宝典.doc");
file5=new VideoFile("笑傲江湖.rmvb");
folder2.add(file1);
folder2.add(file2);
folder3.add(file3);
folder3.add(file4);
folder4.add(file5);
folder1.add(folder2);
folder1.add(folder3);
folder1.add(folder4);
// 从“Sunnt的资料”节点进行杀毒操作
folder1.killVirus();
}
}
结果
模式优点
可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制
客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
增加新的容器构件和叶子构件都很方便,符合开闭原则
为树形结构的面向对象实现提供了一种灵活的解决方案
模式缺点
在增加新构件时很难对容器中的构件类型进行限制
模式适用环境
在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们
在一个使用面向对象语言开发的系统中需要处理一个树形结构
在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型
代理模式
定义
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
引入一个新的代理对象
代理对象在客户端对象和目标对象之间起到中介的作用
去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务
结构
代理模式包含以下3个角色:
Subject(抽象主题角色)
Proxy(代理主题角色)
RealSubject(真实主题角色)
代码
抽象主题类典型代码:
public abstract class Subject {
public abstract void request();
}
真实主题类典型代码
public class RealSubject extends Subject{
public void request() {
//业务方法具体实现代码
}
}
代理类典型代码:
public class Proxy extends Subject {
private RealSubject realSubject = new RealSubject(); //维持一个对真实主题对象的引用
public void preRequest() {
…...
}
public void request() {
preRequest();
realSubject.request(); //调用真实主题对象的方法
postRequest();
}
public void postRequest() {
……
}
}
应用实例
某软件公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求如下:
(1) 在进行商务信息查询之前用户需要通过身份验证,只有合法用户才能够使用该查询系统;
(2) 在进行商务信息查询时系统需要记录查询日志,以便根据查询次数收取查询费用。
该软件公司开发人员已完成了商务信息查询模块的开发任务,现希望能够以一种松耦合的方式向原有系统增加身份验证和日志记录功能,客户端代码可以无区别地对待原始的商务信息查询模块和增加新功能之后的商务信息查询模块,而且可能在将来还要在该信息查询模块中增加一些新的功能。
现使用代理模式设计并实现该收费商务信息查询系统。
实例代码
(1) AccessValidator:身份验证类,业务类
(2) Logger:日志记录类,业务类
(3) Searcher:抽象查询类,充当抽象主题角色
(4) RealSearcher:具体查询类,充当真实主题角色
(5) ProxySearcher:代理查询类,充当代理主题角色
(6) Client:客户端测试类
package 代理模式;
//抽象查询类:抽象主题类
public interface Searcher {
public String doSearcher(String userId,String Keyword);
}
//身份验证类:业务类
class AccessValidator{
//模拟实现登录验证
public boolean validate(String userId){
System.out.println("在数据库中验证用户"+userId+"是否是合法用户?");
if (userId.equalsIgnoreCase("杨过")){
System.out.println(""+userId+"登录成功!");
return true;
}else {
System.out.println(""+userId+"登录失败!");
return false;
}
}
}
//日志记录类:业务类
class Logger{
//模拟实现日志记录
public void log(String userId){
System.out.println("更新数据库,用户"+userId+"查询次数加1!");
}
}
//具体查询类:真实主题类
class RealSeaecher implements Searcher{
@Override
public String doSearcher(String userId, String Keyword) {
System.out.println("用户"+userId+"使用关键词"+Keyword+"查询商务信息!");
return "返回具体内容";
}
}//代理查询类:代理主题类
class ProxySeaecher implements Searcher{
private RealSeaecher seaecher=new RealSeaecher();//维持一个对真实主题的引用
private AccessValidator validator;
private Logger logger;
@Override
public String doSearcher(String userId, String Keyword) {
//如果验证成功,则执行查询
if (validate(userId)){
String result=seaecher.doSearcher(userId,Keyword);//调用真实主题对象的查询方法
this.log(userId);//记录查询日志
return result;//返回查询结果
}
System.out.println("用户"+userId+"使用关键词"+Keyword+"查询商务信息!");
return "返回具体内容";
}
private void log(String userId) {
logger=new Logger();
logger.log(userId);
}
private boolean validate(String userId) {
validator=new AccessValidator();
return validator.validate(userId);
}
}
XMLUtil
package 代理模式;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class XMLUtil {
//该方法用于从XML文件中提取具体类类名,并返回一个实例对象,可以通过参数的不同返回不同的类
//同类名节点所对应的实例
public static Object getBean(){
try {
//创建文档对象
DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=dFactory.newDocumentBuilder();
Document doc;
doc=builder.parse(new File("src/代理模式/config.xml"));
//获取包含类名的文本节点
NodeList nl=doc.getElementsByTagName("className");
Node classNode =nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//同类名生成实例对象并将其返回、
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
config.XML
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<className>代理模式.ProxySeaecher</className>
</configuration>
客户端
public class Client {
public static void main(String[] args) {
Searcher searcher;
searcher=(Searcher) XMLUtil.getBean();
String result=searcher.doSearcher("杨过","玉女心经");
}
}
结果
几种常见的代理模式
远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中,远程代理又称为大使(Ambassador)
动机
客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速地响应并处理客户端的请求
可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在
客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用
虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建
动机
对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理
在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象
使用一个“虚假”的代理对象来代表真实对象,通过代理对象来间接引用真实对象,可以在一定程度上提高系统的性能
保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限
缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等
模式优点
能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性
模式优点——逐个分析
远程代理:可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高了系统的整体运行效率
虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销
缓冲代理:为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间
保护代理:可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限
模式缺点
由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢(例如保护代理)
实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂(例如远程代理)
模式适用环境
当客户端对象需要访问远程主机中的对象时可以使用远程代理
当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理
当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理
当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理
当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理