南宁网站外包,什么是网络营销?请写出其定义,科技进化论,广州手机建设网站#x1f44d;一、创建者模式
#x1f516;1.1、单例模式 单例模式#xff08;Singleton Pattern#xff09;#xff0c;用于确保一个类只有一个实例#xff0c;并提供全局访问点。 在某些情况下#xff0c;我们需要确保一个类只能有一个实例#xff0c;比如数据库连接… 一、创建者模式
1.1、单例模式 单例模式Singleton Pattern用于确保一个类只有一个实例并提供全局访问点。 在某些情况下我们需要确保一个类只能有一个实例比如数据库连接、线程池等。单例模式可以解决这个问题它通过限制类的实例化过程使得一个类只能创建一个对象并提供一个静态方法来获取该对象。 单例模式的关键是将类的构造函数私有化这样外部就无法直接实例化该类。然后我们可以在类内部定义一个静态变量来保存类的唯一实例并提供一个静态方法来获取该实例。这个静态方法会检查实例是否已经存在如果存在则返回现有实例如果不存在则创建一个新实例并返回。
单例设计模式分为两种
饿汉式类加载就会导致该实例对象被创建懒汉式类加载不会导致该实例对象被创建而是首次使用该对象的时候才会创建
①饿汉式-方式1静态变量方式
/*** author 小白程序员* date 2023/8/15 10:14*/
public class Singleton2 {private Singleton2(){}//私有构造方法//在成员位置创建该类的对象private static Singleton2 instance new Singleton2();//对外提供静态方法获取对象public static Singleton2 getInstance(){return instance;}
} 说明该方式在成员位置声明Singleton2类型的静态变量并创建Singleton2类的对象instance。instance对象是随着类的加载而创建的如果该对象足够大的话而一直没有使用就会造成内存的浪费。
②饿汉式-方式2静态代码块方式
/*** author 小白程序员* date 2023/8/15 10:14*/
public class Singleton2 {private Singleton2(){}//私有构造方法//在成员位置创建该类的对象private static Singleton2 instance;static{instance new Singleton2();}//对外提供静态方法获取对象public static Singleton2 getInstance(){return instance;}
} 说明该方式和方式1一样一样会造成内存上的浪费。
③懒汉式-方式1线程不安全
/*** author 小白程序员* date 2023/8/15 10:23*/
public class Role {private Role(){}//私有构造函数//在成员位置创建该类的对象private static Role instance;//对外提供静态方法获取对象public static Role getInstance(){if(instance null){instance new Role();}return instance;}
} 说明该方式在成员位置声明Role类型的静态变量并没有进行对象的赋值操作那么什么时候赋值呢当调用getInstance()方法获取Role类的对象的时候才创建Role对象这样就实现了懒加载的效果但是如果是多线程环境下会出现线程安全问题。
④懒汉式-方式2线程安全双重检查锁
/*** author 小白程序员* date 2023/8/14 17:19*/
public class Student {private static Student instance; //私有静态变量保存唯一实例private Student(){} //私有构造函数防止外部实例化public static Student getInstance(){//第一次判断如果instance不为null不进入枪锁阶段直接返回实例if(instance null){//使用双重检查锁定来确保线程安全synchronized (Student.class){if(instance null){instance new Student();}}}return instance;}public void doSomething(){System.out.println(单例对象做一些事情);}public static void main(String[] args) {Student singleton1 Student.getInstance();Student singleton2 Student.getInstance();System.out.println(singleton1singleton2);singleton1.doSomething();}
}说明上面的双重检查锁模式看上去去完美无缺其实存在问题在多线程情况下可能会出现空指针问题出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。 要解决双重检查锁带来的空指针问题只需要使用volatile关键字可以保证可见性和有序性。
/*** author 小白程序员* date 2023/8/14 17:19*/
public class Student {private static volatile Student instance; //私有静态变量保存唯一实例private Student(){} //私有构造函数防止外部实例化public static Student getInstance(){if(instance null){//使用双重检查锁定来确保线程安全synchronized (Student.class){if(instance null){instance new Student();}}}return instance;} 说明添加volatile关键字之后的双重检查锁是一种较好的单例实现模式能够保证在多线程的情况下线程安全也不会有性能问题。
1.2、工厂模式
①简单工厂模式不属于23种模式的一种
简单工厂包含如下角色 抽象产品 定义了产品的规范描述了产品的主要特性和功能。 具体产品 实现或者继承抽象产品的子类 具体工厂 提供了创建产品的方法调用者通过该方法来获取产品。 在现实生活种我们常常会遇到需要根据不同的条件来创建不同类型的对象的情况。比如我们要创建一个图形绘制程序根据用户选择的图形类型来创建对应的图形对象。这时候简单工厂模式就可以派上用场了。 简单工厂模式的核心思想是将对象的创建封装在一个工厂类中。这个工厂类负责根据客户端的请求来创建具体的对象并将创建的对象返回给客户端使用。 假设我们要创建一个图形绘制程序其中包含不同类型的图形如圆形、矩形和三角形。首先我们定义一个抽象的图形接口
/*** author 小白程序员* Classname Shape* Description 抽象图形接口* date 2023/8/16 15:32*/
public interface Shape {void draw();
}定义三个具体的图形类分别实现了抽象图形接口。
/*** author 小白程序员* Classname Circle* Description 圆形类* date 2023/8/16 15:33*/
public class Circle implements Shape {Overridepublic void draw() {System.out.println(画圆形);}
}/*** author 小白程序员* Classname Rectangle* Description 矩形类* date 2023/8/16 15:34*/
public class Rectangle implements Shape{Overridepublic void draw() {System.out.println(矩形);}
}/*** author 小白程序员* Classname Triangle* Description 三角形* date 2023/8/16 15:34*/
public class Triangle implements Shape{Overridepublic void draw() {System.out.println(三角形);}
}定义简单工厂类用于客户端的请求来创建具体的图形对象。
/*** author 小白程序员* Classname ShapeFactory* Description 简单工厂类* date 2023/8/16 15:36*/
public class ShapeFactory {public static Shape createShape(String shapeType){if(shapeType.equalsIgnoreCase(CIRCLE)){return new Circle();}else if(shapeType.equalsIgnoreCase(RECTANGLE)){return new Rectangle();}else if(shapeType.equalsIgnoreCase(TRIANGLE)){return new Triangle();}else{System.out.println(没有找到对应的形状);return null;}}public static void main(String[] args) {//创建图形对象Shape circle ShapeFactory.createShape(CIRCLE);if (circle ! null) {circle.draw();}Shape rectangle ShapeFactory.createShape(RECTANGLE);if (rectangle ! null) {rectangle.draw();}Shape triangle ShapeFactory.createShape(TRIANGLE);if (triangle ! null) {triangle.draw();}}
}优点 封装了创建对象的过程可以通过参数直接获取对象。把对象的创建和业务逻辑层分开这样以后就避免了修改客户代码如果要实现新产品直接修改工厂类而不需要在原代码中修改这样就降低了客户代码修改的可能性更加容易扩展。
缺点 增加新产品时还是需要修改工厂类的代码违背了“开闭原则”。
扩展静态工厂实际很简单就是简单工厂的工厂方法设置成static。
②工厂方法模式
工厂方法模式的主要角色 抽象工厂Abstract Factory提供了创建产品的接口调用者通过它访问具体工厂的工厂方法来创建产品。 具体工厂ConcreteFactory主要是实现抽象工厂中的抽象方法完成具体产品的创建。 抽象产品Product定义了产品的规范描述了产品的主要特性和功能。 具体产品ConcreteProduct实现了抽象产品角色所定义的接口由具体工厂来创建它同具体工厂之间一一对应。 工厂方法模式用于定义一个创建对象的接口但由子类决定实例化哪个类。简单来说就是将对象的创建延迟到子类中进行。 工厂方法模式的核心思想是将对象的创建封装在一个工厂接口中每个具体的工厂类负责创建特定类型的对象。客户端通过调用工厂接口的方法来获取所需的对象而无需关心具体的类是什么。 假设我们要创建一个电脑制造工厂其中包含不同品牌的电脑如苹果电脑、华为电脑和联想电脑。首先我们定义一个抽象的电脑接口
/*** author 小白程序员* Classname Computer* Description 抽象电脑接口* date 2023/8/16 15:58*/
public interface Computer {void playGame();
}定义具体的电脑类分别实现了抽象电脑接口
/*** author 小白程序员* Classname AppleComputer* Description 苹果电脑* date 2023/8/16 15:59*/
public class AppleComputer implements Computer{Overridepublic void playGame() {System.out.println(苹果电脑玩游戏);}
}/*** author 小白程序员* Classname HuaWeiComputer* Description 华为电脑* date 2023/8/16 16:00*/
public class HuaWeiComputer implements Computer{Overridepublic void playGame() {System.out.println(华为手机玩游戏);}
}/*** author 小白程序员* Classname LenovoComputer* Description 联想电脑* date 2023/8/16 16:01*/
public class LenovoComputer implements Computer{Overridepublic void playGame() {System.out.println(联想游戏);}
}接下来定义工厂接口用户创建电脑的对象
/*** author 小白程序员* Classname ComputerFactory* Description 工厂接口* date 2023/8/16 16:02*/
public interface ComputerFactory {Computer createComputer();
}然后我们分别实现具体的工厂类每个工厂类负责创建特点品牌的电脑对象
/*** author 小白程序员* Classname AppleComputerFactory* Description 苹果电脑工厂* date 2023/8/16 16:04*/
public class AppleComputerFactory implements ComputerFactory{Overridepublic Computer createComputer() {return new AppleComputer();}
}/*** author 小白程序员* Classname HuaWeiComputerFactory* Description 华为电脑工厂* date 2023/8/16 16:05*/
public class HuaWeiComputerFactory implements ComputerFactory{Overridepublic Computer createComputer() {return new HuaWeiComputer();}
}/*** author 小白程序员* Classname LenovoComputerFactory* Description 联想电脑工厂* date 2023/8/16 16:06*/
public class LenovoComputerFactory implements ComputerFactory{Overridepublic Computer createComputer() {return new LenovoComputer();}
}
/*** author 小白程序员* Classname Main* Description 测试* date 2023/8/16 16:06*/
public class Main {public static void main(String[] args) {//创建苹果电脑对象AppleComputerFactory appleComputerFactory new AppleComputerFactory();Computer appleComputer appleComputerFactory.createComputer();appleComputer.playGame();//创建华为电脑对象HuaWeiComputerFactory huaWeiComputerFactory new HuaWeiComputerFactory();Computer huaweiComputer huaWeiComputerFactory.createComputer();huaweiComputer.playGame();//创建联想电脑对象LenovoComputerFactory lenovoComputerFactory new LenovoComputerFactory();Computer lenovoComputer lenovoComputerFactory.createComputer();lenovoComputer.playGame();}
}通过工厂方法模式我们可以根据客户端的请求来创建不同品牌的电脑对象而无需关心具体的类是什么。这样我们可以方便地扩展和修改对象的创建逻辑同时也符合开闭原则。、
优点 用户只需要知道具体工厂的名称就可得到所要的产品无须知道产品的具体创建过程 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类无须对原工厂进行任何修改满足开闭原则
缺点 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类这增加了系统的复杂度。
自我理解定义一个抽象的工厂接口其中包含创建对象的接口然后我们可以创建多个具体的对象类每个类去实现抽象工厂的接口负责创建特定对象。 ③抽象工厂模式 抽象工厂模式Abstract Factory Pattern用于提供一个接口用于创建相关或依赖对象的家族而不需要指定具体的类。 前面介绍的工厂方法模式中考虑的是一类产品的生产如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。 这些工厂只生产同种类产品同种类产品称为同等级产品也就是说工厂方法模式只考虑生产同等级的产品但是在现实生活中许多工厂是综合型的工厂能生产多等级种类 的产品如电器厂既生产电视机又生产洗衣机或空调大学既有软件专业又有生物专业等。 本节要介绍的抽象工厂模式将考虑多等级产品的生产将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族下图所示横轴是产品等级也就是同一类产品纵轴是产品族也就是同一品牌的产品同一品牌的产品产自同一个工厂。 抽象工厂模式的主要角色如下 抽象工厂Abstract Factory提供了创建产品的接口它包含多个创建产品的方法可以创建多个不同等级的产品。 具体工厂Concrete Factory主要是实现抽象工厂中的多个抽象方法完成具体产品的创建。 抽象产品Product定义了产品的规范描述了产品的主要特性和功能抽象工厂模式有多个抽象产品。 具体产品ConcreteProduct实现了抽象产品角色所定义的接口由具体工厂来创建它 同具体工厂之间是多对一的关系。 在现实生活中我们经常会遇到需要创建一组相关的对象的情况。比如我们要创建一个家具工厂包括创建椅子、桌子和柜子等家具。这时候抽象工厂模式就可以派上用场。 抽象工厂模式的核心思想是将一组相关的对象的创建封装在一个抽象工厂接口中每个具体的工厂类负责创建特定类型的对象。客户端通过调用抽象工厂接口的方法来获取所需的对象而无需关心具体的类是什么。 假设我们要创建一个家具工厂其中包含不同类型的家具如现代风格的椅子、桌子和柜子以及传统风格的椅子、桌子和柜子。首先我们定义一个抽象的家具接口
/*** author 小白程序员* Classname Chair* Description 抽象椅子接口* date 2023/8/16 16:38*/
public interface Chair {void sit();
}/*** author 小白程序员* Classname Cabinet* Description 抽象柜子接口* date 2023/8/16 16:39*/
public interface Cabinet {void open();
}/*** author 小白程序员* Classname Table* Description 抽象桌子接口* date 2023/8/16 16:38*/
public interface Table {void use();
}
然后我们定义了具体的家具系列分别实现了抽象家具接口
/*** author 小白程序员* Classname ModernChair* Description 现代风格椅子类* date 2023/8/16 16:42*/
public class ModernChair implements Chair{Overridepublic void sit() {System.out.println(现代椅子可以坐下);}
}/*** author 小白程序员* Classname ModernTable* Description 现代风格桌子类* date 2023/8/16 16:43*/
public class ModernTable implements Table{Overridepublic void use() {System.out.println(使用现代 TABLE);}
}/*** author 小白程序员* Classname ModernCabinet* Description 现代风格柜子类* date 2023/8/16 16:44*/
public class ModernCabinet implements Cabinet{Overridepublic void open() {System.out.println(modernCabinet 打开);}
}接下来我们定义一个抽象工厂接口用于创建家具对象
/*** author 小白程序员* Classname FurnitureFactory* Description 抽象家具工厂接口* date 2023/8/16 16:47*/
public interface FurnitureFactory {Chair createChair();Table createTable();Cabinet createCabinet();}
然后我们分别实现具体的工厂类每个工厂类负责创建特定风格的家具对象
/*** author 小白程序员* Classname ModernFurnitureFactory* Description 家具工厂类* date 2023/8/16 16:52*/
public class ModernFurnitureFactory implements FurnitureFactory{Overridepublic Chair createChair() {return new ModernChair();}Overridepublic Table createTable() {return new ModernTable();}Overridepublic Cabinet createCabinet() {return new ModernCabinet();}
}现在我们可以使用抽象工厂模式来创建不同风格的家具对象了
/*** author 小白程序员* Classname Main* Description 测试* date 2023/8/16 16:54*/
public class Main {public static void main(String[] args) {// 创建现代风格家具对象FurnitureFactory modernFactory new ModernFurnitureFactory();Chair modernChair modernFactory.createChair();Table modernTable modernFactory.createTable();Cabinet modernCabinet modernFactory.createCabinet();modernChair.sit();modernTable.use();modernCabinet.open();}
} 通过抽象工厂模式我们可以根据客户端的需求来创建不同风格的家具对象而无需关心具体的类是什么。这样我们可以方便地扩展和修改家具的创建逻辑同时也符合开闭原则。
优点 当一个产品族中的多个对象被设计成一起工作时它能保证客户端始终只使用同一个产品族中的对象。
缺点 当产品族中需要增加一个新的产品时所有的工厂类都需要进行修改。
使用场景 当需要创建的对象是一系列相互关联或相互依赖的产品族时如电器工厂中的电视机、洗衣机、空调等。 系统中有多个产品族但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。 系统中提供了产品的类库且所有产品的接口相同客户端不依赖产品实例的创建细节和内部结构。
如输入法换皮肤一整套一起换。生成不同操作系统的程序。
自我理解抽象工厂模式是把创建对象的方法封装到了一个接口中在实现中把这些对象都实现。
1.3、原型模式 原型模式用于通过复制现有对象来创建新对象。 想象一下你正在建造一个城市需要创建许多相似的建筑物比如房屋、商店和办公楼。为了避免每次都从头开始构建这些建筑物你可以使用原型模式。 具体来说你可以创建一个原型接口或抽象类其中包含一个克隆方法。然后你可以创建多个具体的原型类每个类都实现了克隆方法并且能够复制自己。 当你需要创建新的建筑物时只需从适当的原型对象进行克隆即可而无需重新构建整个对象。
原型模式包含如下角色 抽象原型类规定了具体原型对象必须实现的的 clone() 方法。 具体原型类实现抽象原型类的 clone() 方法它是可被复制的对象。 访问类使用具体原型类中的 clone() 方法来复制新的对象。 原型模式的克隆分为浅克隆和深克隆 浅克隆创建一个新对象新对象的属性和原来对象完全相同对于非基本类型属性仍指向原有属性所指向的对象的内存地址。 深克隆创建一个新对象属性中引用的其他对象也会被克隆不再指向原有对象地址。 Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类而实现了Cloneable接口的子实现类就是具体的原型类。 浅拷贝——方式一
/*** author 小白程序员* Classname BuildingPrototype* Description 建筑物原型接口* date 2023/8/16 20:14*/
public interface BuildingPrototype {BuildingPrototype clone();String getType();}/*** author 小白程序员* Classname House* Description 房屋类* date 2023/8/16 20:16*/
public class House implements BuildingPrototype{private String type;public House(String type) {this.type type;}Overridepublic BuildingPrototype clone() {return new House(this.type);}public void setType(String type) {this.type type;}Overridepublic String getType() {return type;}
}/*** author 小白程序员* Classname Shop* Description 商店类* date 2023/8/16 20:19*/
public class Shop implements BuildingPrototype{private String type;public Shop(String type) {this.type type;}public void setType(String type){this.type type;}Overridepublic String getType(){return type;}Overridepublic BuildingPrototype clone() {return new Shop(this.type);}
}/*** author 小白程序员* Classname Main* Description 测试* date 2023/8/16 20:21*/
public class Main {public static void main(String[] args) {BuildingPrototype housePrototype new House(单层别墅);BuildingPrototype clonedHouse housePrototype.clone();System.out.println(clonedHouse.getType());BuildingPrototype shopPrototype new Shop(二层商铺);BuildingPrototype clonedShop shopPrototype.clone();System.out.println(clonedShop.getType());}
} 通过原型模式你可以复制现有的建筑物对象来创建新的对象而无需重新构建整个对象。这样你可以节省时间和资源并且方便地定制每个新对象的属性。 需要注意的是克隆方法可以实现浅拷贝或深拷贝具体取决于你的需求。浅拷贝只复制对象的基本属性而深拷贝会递归复制对象的所有引用类型属性。
深拷贝——方式二
//奖状类
public class Citation implements Cloneable {private Student stu;public Student getStu() {return stu;}public void setStu(Student stu) {this.stu stu;}void show() {System.out.println(stu.getName() 同学在2020学年第一学期中表现优秀被评为三好学生。特发此状);}Overridepublic Citation clone() throws CloneNotSupportedException {return (Citation) super.clone();}
}//学生类
public class Student {private String name;private String address;public Student(String name, String address) {this.name name;this.address address;}public Student() {}public String getName() {return name;}public void setName(String name) {this.name name;}public String getAddress() {return address;}public void setAddress(String address) {this.address address;}
}//测试类
public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {Citation c1 new Citation();Student stu new Student(张三, 西安);c1.setStu(stu);//复制奖状Citation c2 c1.clone();//获取c2奖状所属学生对象Student stu1 c2.getStu();stu1.setName(李四);//判断stu对象和stu1对象是否是同一个对象System.out.println(stu和stu1是同一个对象 (stu stu1));c1.show();c2.show();}
}
说明stu对象和stu1对象是同一个对象就会产生将stu1对象中name属性值改为“李四”两个Citation对象中显示的都是李四这就是浅克隆的效果对具体原型类Citation中的引用类型的属性进行引用复制。这种情况需要使用深克隆而进行深克隆需要使用对象流。
public class CitationTest1 {public static void main(String[] args) throws Exception {Citation c1 new Citation();Student stu new Student(张三, 西安);c1.setStu(stu);//创建对象输出流对象ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(C:\\Users\\Think\\Desktop\\b.txt));//将c1对象写出到文件中oos.writeObject(c1);oos.close();//创建对象出入流对象ObjectInputStream ois new ObjectInputStream(new FileInputStream(C:\\Users\\Think\\Desktop\\b.txt));//读取对象Citation c2 (Citation) ois.readObject();//获取c2奖状所属学生对象Student stu1 c2.getStu();stu1.setName(李四);//判断stu对象和stu1对象是否是同一个对象System.out.println(stu和stu1是同一个对象 (stu stu1));c1.show();c2.show();}
} 注意Citation类和Student类必须实现Serializable接口否则会抛NotSerializableException异常。 自我理解在我们需要创建一些许多相似对象的时候可以创建一个原型接口或者抽象类其中需要有克隆的方法之后可以创建多个具体的对象类每个类实现克隆的方法实现复制效果。
1.4、建造者模式
将一个复杂对象的构建与表示分离。 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于某个对象的构建过程复杂的情况。 由于实现了构建和装配的解耦。不同的构建器相同的装配也可以做出不同的对象相同的构建器不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦实现了更好的复用。 建造者模式可以将部件和其组装过程分开一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象而无须知道其内部的具体构造细节。
建造者Builder模式包含如下角色 抽象建造者类Builder这个接口规定要实现复杂对象的那些部分的创建并不涉及具体的部件对象的创建。 具体建造者类ConcreteBuilder实现 Builder 接口完成复杂产品的各个部件的具体创建方法。在构造过程完成后提供产品的实例。 产品类Product要创建的复杂对象。 指挥者类Director调用具体建造者来创建复杂对象的各个部分在指导者中不涉及具体产品的信息只负责保证对象各部分完整创建或按某种顺序创建。 想象一下你正在建造一座房子。建造一个房子涉及到多个步骤比如地基的建设、墙壁的搭建、屋顶的安装等。而且不同的房子可能有不同的建造流程和组件。 使用建造者模式你可以将每个构建步骤封装在单独的建造者类中并通过一个指导者类来协调这些步骤的执行。最终你可以根据需要选择不同的建造者来构建出不同类型的房子。
/*** author 小白程序员* Classname House* Description 房屋类* date 2023/8/16 21:46*/
Data
public class House {private String foundation;private String walls;private String roof;}/*** author 小白程序员* Classname HouseBuilder* Description 房屋建造者* date 2023/8/16 21:49*/
Getter
public abstract class HouseBuilder {protected House house;public void createNewHouse(){house new House();}public abstract void buildWalls();public abstract void buildRoof();public abstract void buildFoundation();
}/*** author 小白程序员* Classname VillaBuilder* Description 别墅建造者* date 2023/8/16 22:06*/
public class VillaBuilder extends HouseBuilder{Overridepublic void buildWalls() {house.setWalls(大理石墙);}Overridepublic void buildRoof() {house.setRoof(玻璃屋顶);}Overridepublic void buildFoundation() {house.setFoundation(钢筋混凝土地基);}
}/*** author 小白程序员* Classname Director* Description 指导者* date 2023/8/16 22:12*/
public class Director {private HouseBuilder houseBuilder;public void setHouseBuilder(HouseBuilder houseBuilder) {this.houseBuilder houseBuilder;}public House getHouse(){return houseBuilder.getHouse();}public void constructHouse(){houseBuilder.createNewHouse();houseBuilder.buildWalls();houseBuilder.buildRoof();houseBuilder.buildFoundation();}
}/*** author 小白程序员* Classname Main* Description 使用建造者模式构建房子* date 2023/8/16 22:21*/
public class Main {public static void main(String[] args) {Director director new Director();VillaBuilder villaBuilder new VillaBuilder();director.setHouseBuilder(villaBuilder);director.constructHouse();House villa director.getHouse();System.out.println(villa);}
} 在这个示例中House类表示房子对象HouseBuilder类是建造者类。你可以通过调用HouseBuilder类的方法来设置房子的属性然后通过调用getHouse()方法获取最终构建好的房子对象。 使用建造者模式你可以根据需要选择性地设置房子的属性而不必关心构建过程的细节。这样可以使得代码更加清晰、易于维护并且能够应对复杂对象的创建需求。
优点 建造者模式的封装性很好。使用建造者模式可以有效的封装变化在使用建造者模式的场景中一般产品类和建造者类是比较稳定的因此将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。 在建造者模式中客户端不必知道产品内部组成的细节将产品本身与产品的创建过程解耦使得相同的创建过程可以创建不同的产品对象。 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中使得创建过程更加清晰也更方便使用程序来控制创建过程。 建造者模式很容易进行扩展。如果有新的需求通过实现一个新的建造者类就可以完成基本上不用修改之前已经测试通过的代码因此也就不会对原有功能引入风险。符合开闭原则。
缺点 造者模式所创建的产品一般具有较多的共同点其组成部分相似如果产品之间的差异性很大则不适合使用建造者模式因此其使用范围受到一定的限制。
使用场景
建造者Builder模式创建的是复杂对象其产品的各个部分经常面临着剧烈的变化但将它们组合在一起的算法却相对稳定所以它通常在以下场合使用。 创建的对象较复杂由多个部件构成各部件面临着复杂的变化但构件间的建造顺序是稳定的。 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式即产品的构建过程和最终的表示是独立的。
扩展 建造者模式除了上面的用途外在开发中还有一个常用的使用方式就是当一个类构造器需要传入很多参数时如果创建这个类的实例代码可读性会非常差而且很容易引入错误此时就可以利用建造者模式进行重构。
重构前代码如下
public class Phone {private String cpu;private String screen;private String memory;private String mainboard;public Phone(String cpu, String screen, String memory, String mainboard) {this.cpu cpu;this.screen screen;this.memory memory;this.mainboard mainboard;}public String getCpu() {return cpu;}public void setCpu(String cpu) {this.cpu cpu;}public String getScreen() {return screen;}public void setScreen(String screen) {this.screen screen;}public String getMemory() {return memory;}public void setMemory(String memory) {this.memory memory;}public String getMainboard() {return mainboard;}public void setMainboard(String mainboard) {this.mainboard mainboard;}Overridepublic String toString() {return Phone{ cpu cpu \ , screen screen \ , memory memory \ , mainboard mainboard \ };}
}public class Client {public static void main(String[] args) {//构建Phone对象Phone phone new Phone(intel,三星屏幕,金士顿,华硕);System.out.println(phone);}
} 上面在客户端代码中构建Phone对象传递了四个参数如果参数更多呢代码的可读性及使用的成本就是比较高。
重构后代码
public class Phone {private String cpu;private String screen;private String memory;private String mainboard;private Phone(Builder builder) {cpu builder.cpu;screen builder.screen;memory builder.memory;mainboard builder.mainboard;}public static final class Builder {private String cpu;private String screen;private String memory;private String mainboard;public Builder() {}public Builder cpu(String val) {cpu val;return this;}public Builder screen(String val) {screen val;return this;}public Builder memory(String val) {memory val;return this;}public Builder mainboard(String val) {mainboard val;return this;}public Phone build() {return new Phone(this);}}Overridepublic String toString() {return Phone{ cpu cpu \ , screen screen \ , memory memory \ , mainboard mainboard \ };}
}public class Client {public static void main(String[] args) {Phone phone new Phone.Builder().cpu(intel).mainboard(华硕).memory(金士顿).screen(三星).build();System.out.println(phone);}
} 重构后的代码在使用起来更方便某种程度上也可以提高开发效率。从软件设计上对程序员的要求比较高。
1.5创建者模式对比
1.5.1工厂方法模式VS建造者模式 工厂方法模式 目的定义一个用于创建对象的接口但将具体的对象创建延迟到子类中进行。应用场景适用于需要根据不同的情况或条件创建不同类型的对象并且这些对象都实现了同一接口或继承了同一基类。示例假设有一个抽象产品接口有多个具体产品实现类每个具体产品都有对应的工厂类来创建该产品对象。 建造者模式 目的将一个复杂对象的构建过程与其表示分离使得同样的构建过程可以创建不同的表示。应用场景适用于创建复杂对象的情况其中构建过程包含多个步骤并且不同的构建顺序和配置可以生成不同的对象。示例假设有一个汽车建造者接口有多个具体建造者实现类每个具体建造者都能按照一定的步骤和配置构建汽车对象。
区别总结
工厂方法模式关注的是通过不同的工厂类来创建不同类型的对象每个工厂类只负责创建一种产品建造者模式关注的是通过一个统一的建造者接口或类来构建复杂对象可以按照不同的步骤和配置生成不同的对象表示。
简而言之工厂方法模式适用于需要根据情况创建不同类型的对象而建造者模式适用于创建复杂对象的场景并且可以灵活地配置对象的构建过程。
1.5.2抽象工厂模式VS建造者模式 抽象工厂模式Abstract Factory Pattern 目的提供一个接口用于创建一系列相关或相互依赖的对象而无需指定具体的类。应用场景适用于需要创建一组相关对象的情况这些对象通常具有共同的接口或基类并且可以通过切换具体工厂来改变整个产品族的创建过程。示例假设有一个汽车工厂接口有两个具体工厂实现类分别生产轿车和SUV每个具体工厂都能创建对应类型的汽车对象。 建造者模式Builder Pattern 目的将一个复杂对象的构建过程与其表示分离使得同样的构建过程可以创建不同的表示。应用场景适用于创建复杂对象的情况其中构建过程包含多个步骤并且不同的构建顺序和配置可以生成不同的对象。示例假设有一个汽车建造者接口有两个具体建造者实现类分别用于构建轿车和SUV每个具体建造者都能按照一定的步骤和配置构建对应类型的汽车对象。
总结区别
抽象工厂模式关注的是创建一系列相关对象通过切换具体工厂来改变整个产品族的创建过程建造者模式关注的是创建复杂对象通过不同的构建顺序和配置可以生成不同的对象表示。
简而言之抽象工厂模式着重于创建一组相关对象而建造者模式则专注于构建复杂对象。 二、结构型模式
2.1代理模式 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时访问对象不适合或者不能直接引用目标对象代理对象作为访问对象和目标对象之间的中介。 Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。 想象一下你要买一本书但你不想亲自去书店。于是你找了一个代购他会帮你去书店购买并送到你手上。在这个例子中代购就是一个代理对象他代表你去执行购买书籍的任务。 在软件开发中代理模式也是类似的。它可以为其他对象提供一个替代品或占位符以控制对这些对象的访问。代理对象和被代理对象实现相同的接口使得代理对象可以在不改变客户端代码的情况下对被代理对象进行控制和增强。
代理Proxy模式分为三种角色 抽象主题Subject类 通过接口或抽象类声明真实主题和代理对象实现的业务方法。 真实主题Real Subject类 实现了抽象主题中的具体业务是代理对象所代表的真实对象是最终要引用的对象。 代理Proxy类 提供了与真实主题相同的接口其内部含有对真实主题的引用它可以访问、控制或扩展真实主题的功能。
①静态代理案例
/*** author 小白程序员* Classname Image* Description Image接口* date 2023/8/17 10:58*/
public interface Image {void display();
}/*** author 小白程序员* Classname RealImage* Description image的具体实现类* date 2023/8/17 11:02*/
public class RealImage implements Image{private String fileName;public RealImage(String fileName) {this.fileName fileName;loadFromDisk();}private void loadFromDisk() {System.out.println(从磁盘加载fileName图片fileName);}Overridepublic void display() {System.out.println(显示fileName图片fileName);}
}/*** author 小白程序员* Classname ImageProxy* Description 代理类* date 2023/8/17 11:04*/
public class ImageProxy implements Image{private String fileName;private RealImage realImage;public ImageProxy(String fileName) {this.fileName fileName;}Overridepublic void display() {if(realImage null) {realImage new RealImage(fileName);}realImage.display();}
}/*** author 小白程序员* Classname Client* Description 测试* date 2023/8/17 11:07*/
public class Client {public static void main(String[] args) {Image image new ImageProxy(image.jpg);//第一次调用displag()方法时会创建并加载真实的图片对象image.display();//第二次调用display()方法时会直接使用已经创建的真实图片对象image.display();}
} 解释一下当第一次调用display()方法的时候会调用loadFromDisk()方法所以输出了第一句话在第二次调用时判断了realImage是否为null所以就不会再次创建了。ImageProxy 就是代理类它实现了 Image 接口并持有一个 RealImage 对象的引用。当调用 display() 方法时代理类会通过调用真实图片对象的 display() 方法来完成显示操作。
②动态代理
JDK动态代理
/*** author 小白程序员* Classname Image* Description image接口* date 2023/8/17 11:19*/
public interface Image {void display();
}/*** author 小白程序员* Classname RealImage* Description Image接口的实现类* date 2023/8/17 11:20*/
public class RealImage implements Image{private String fileName;public RealImage(String fileName) {this.fileName fileName;loadFromDisk();}private void loadFromDisk() {System.out.println(从磁盘加载图片);}Overridepublic void display() {System.out.println(显示图片);}
}/*** author 小白程序员* Classname ImageProxyHandler* Description 工厂* date 2023/8/17 11:33*/
public class ProxyFactory implements InvocationHandler{private Object realObject;public ProxyFactory(Object realObject){this.realObject realObject;}Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(代理开始);Object result method.invoke(realObject, args);System.out.println(代理结束);return result;}
}/*** author 小白程序员* Classname Client* Description 测试* date 2023/8/17 11:44*/
public class Client {public static void main(String[] args) {Image realImage new RealImage(image.jpg);Image proxyImage (Image) Proxy.newProxyInstance(realImage.getClass().getClassLoader(),realImage.getClass().getInterfaces(),new ProxyFactory(realImage));proxyImage.display();}
}
CGLIB动态代理要引入CGLIB依赖
/*** author 小白程序员* Classname Image1* Description Image1* date 2023/8/17 13:12*/
public class Image1 {public void display(){System.out.println(Image1);}
}/*** author 小白程序员* Classname ImageMethodInterceptor* Description 代理类* date 2023/8/17 12:47*/
public class ImageMethodInterceptor implements MethodInterceptor{/*intercept方法参数说明obj 代理对象method 真实对象中的方法的Method实例args 实际参数proxy 代理对象中的方法的method实例*/Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {//在调用代理对象的方法之前执行一些额外的操作System.out.println(代理开始执行了);//调用真实对象的方法Object result proxy.invokeSuper(obj, args);//在调用代理对象的方法之后执行一些额外的操作System.out.println(代理执行结束了);return result;}
}/*** author 小白程序员* Classname CglibProxyExample* Description 测试* date 2023/8/17 12:52*/
public class CglibProxyExample {public static void main(String[] args) {Enhancer enhancer new Enhancer();enhancer.setSuperclass(Image1.class);enhancer.setCallback(new ImageMethodInterceptor());Image1 proxyImage (Image1)enhancer.create();proxyImage.display();}
} 在这两个示例中我们分别使用了JDK动态代理和CGLIB动态代理来创建代理对象。JDK动态代理要求被代理类实现一个接口而CGLIB动态代理则可以直接代理普通的类。
❗JDK动态代理、CGLIB动态代理、静态代理对比 JDK动态代理是基于接口的代理。它通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。JDK动态代理要求目标对象必须实现一个或多个接口。代理对象会实现这些接口并将方法调用委托给InvocationHandler处理器进行处理。因为基于接口的代理所以只能代理接口中定义的方法。 CGLIB动态代理是基于继承的代理。它通过继承目标对象创建一个子类并重写目标对象的方法来实现代理。CGLIB动态代理不需要目标对象实现接口它可以代理普通的类。CGLIB动态代理通过生成字节码来创建代理类因此在运行时性能较JDK动态代理略低。 JDK动态代理是Java标准库的一部分无需引入额外的依赖。而CGLIB动态代理则需要引入CGLIB库作为依赖。 JDK动态代理适用于对接口进行代理的场景比如Spring AOP中的切面编程。而CGLIB动态代理适用于对类进行代理的场景比如在没有接口的情况下对类进行代理。 总体来说如果你需要代理接口中的方法可以选择JDK动态代理如果你需要代理普通类中的方法或者目标对象没有实现接口可以选择CGLIB动态代理。 jdk代理和CGLIB代理 使用CGLib实现动态代理CGLib底层采用ASM字节码生成框架使用字节码技术生成代理类在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是CGLib不能对声明为final的类或者方法进行代理因为CGLib原理是动态生成被代理类的子类。 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后在调用次数较少的情况下JDK代理效率高于CGLib代理效率只有当进行大量调用的时候JDK1.6和JDK1.7比CGLib代理效率低一点但是到JDK1.8的时候JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理如果没有接口使用CGLIB代理。 动态代理和静态代理 动态代理与静态代理相比较最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理InvocationHandler.invoke。这样在接口方法数量比较多的时候我们可以进行灵活处理而不需要像静态代理那样每一个方法进行中转。 如果接口增加一个方法静态代理模式除了所有实现类需要实现这个方法外所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
优点 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用 代理对象可以扩展目标对象的功能 代理模式能将客户端与目标对象分离在一定程度上降低了系统的耦合度
缺点 增加了系统的复杂度
使用场景 远程Remote代理 本地服务通过网络请求远程服务。为了实现本地到远程的通信我们需要实现网络通信处理其中可能的异常。为良好的代码设计和可维护性我们将网络通信部分隐藏起来只暴露给本地服务一个接口通过该接口即可访问远程服务提供的功能而不必过多关心通信部分的细节。 防火墙Firewall代理 当你将浏览器配置成使用代理功能时防火墙就将你的浏览器的请求转给互联网当互联网返回响应时代理服务器再把它转给你的浏览器。 保护Protect or Access代理 控制对一个对象的访问如果需要可以给不同的用户提供不同级别的使用权限。 2.2适配器模式 适配器模式是一种结构型设计模式它用于将一个类的接口转换成客户端所期望的另一个接口。通俗地说适配器模式就像是一个插头转换器它可以让你在不改变原有插座的情况下使用不同类型的插头。 举个例子来说明适配器模式。假设你有一个音乐播放器它只能播放MP3格式的音乐文件。但是你现在有一些其他格式的音乐文件比如WAV和FLAC你希望能够在音乐播放器上播放这些文件。这时候你可以使用适配器模式。 适配器模式的核心思想就是通过一个适配器类来将不兼容的接口转换为兼容的接口从而让不同的类能够协同工作。它可以帮助你在不改变已有代码的情况下实现接口之间的互相适配。 适配器模式分为类适配器模式和对象适配器模式前者类之间的耦合度比后者高且要求程序员了解现有组件库中的相关组件的内部结构所以应用相对较少些。
适配器模式Adapter包含以下主要角色 目标Target接口当前系统业务所期待的接口它可以是抽象类或接口。 适配者Adaptee类它是被访问和适配的现存组件库中的组件接口。 适配器Adapter类它是一个转换器通过继承或引用适配者的对象把适配者接口转换成目标接口让客户按目标接口的格式访问适配者。
①类适配器模式 它通过多重继承来实现适配器功能。在类适配器模式中适配器类同时继承了目标接口和被适配者类从而可以将被适配者的方法转换为目标接口所期望的方法。 /*** author 小白程序员* Classname Target* Description 目标接口* date 2023/8/17 16:08*/
public interface Target {void request();
}/*** author 小白程序员* Classname Adaptee* Description 被适配者类* date 2023/8/17 16:09*/
public class Adaptee {public void specificRequest(){System.out.println(被适配者方法);}
}/*** author 小白程序员* Classname Adapter* Description 适配器类* date 2023/8/17 16:10*/
public class Adapter extends Adaptee implements Target{Overridepublic void request() {specificRequest();//调用被适配者类的方法}
}/*** author 小白程序员* Classname Main* Description 测试* date 2023/8/17 16:12*/
public class Main {public static void main(String[] args) {Target target new Adapter();target.request();}
} 在这个示例中Adapter类继承了Adaptee类并实现了Target接口。当客户端调用request方法时实际上是通过适配器类间接调用了被适配者类的specificRequest方法。 类适配器模式的优点是可以重用已有的代码而不需要修改目标接口和被适配者类。但它也有一个限制即适配器类只能适配一个被适配者类。
②对象适配器模式 它通过组合关系将一个类的接口转换成客户端所希望的另一个接口在对象适配器模式中适配器类持有一个被适配者类的实例并实现了目标接口。 /*** author 小白程序员* Classname Target* Description 目标接口* date 2023/8/17 16:08*/
public interface Target {void request();
}/*** author 小白程序员* Classname Adaptee* Description 被适配者类* date 2023/8/17 16:09*/
public class Adaptee {public void specificRequest(){System.out.println(被适配者方法);}
}/*** author 小白程序员* Classname Adapter* Description 适配器类* date 2023/8/17 16:10*/
public class Adapter implements Target {private Adaptee adaptee;public Adapter(Adaptee adaptee) {this.adaptee adaptee;}Overridepublic void request() {adaptee.specificRequest();}
}/*** author 小白程序员* Classname Main* Description 测试* date 2023/8/17 16:37*/
public class Main {public static void main(String[] args) {Adaptee adaptee new Adaptee();Target target new Adapter(adaptee);target.request();}
}在这个示例中Adapter类持有一个Adaptee类的实例并实现了Target接口。当客户端调用request方法时实际上是通过适配器类间接调用了被适配者类的specificRequest方法。 对象适配器模式的优点是可以适配多个被适配者类并且不需要修改目标接口和被适配者类。但它也有一个限制即适配器类只能适配与目标接口兼容的被适配者类。 注意还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时可以创建一个抽象类Adapter 实现所有方法。而此时我们只需要继承该抽象类即可。 应用场景 以前开发的系统存在满足新系统功能需求的类但其接口同新系统的接口不一致。 使用第三方提供的组件但组件接口定义和自己要求的接口定义不同。 2.3装饰者模式 装饰者模式是一种结构型设计模式它允许你在不改变现有对象结构的情况下动态地将新功能添加到对象上。装饰者模式通过创建一个包装对象来实现在保持接口的同时增加了额外的行为。
装饰Decorator模式中的角色 抽象构件Component角色 定义一个抽象接口以规范准备接收附加责任的对象。 具体构件Concrete Component角色 实现抽象构件通过装饰角色为其添加一些职责。 抽象装饰Decorator角色 继承或实现抽象构件并包含具体构件的实例可以通过其子类扩展具体构件的功能。 具体装饰ConcreteDecorator角色 实现抽象装饰的相关方法并给具体构件对象添加附加的责任。
/*** author 小白程序员* Classname Component* Description 基础组件库* date 2023/8/17 17:21*/
public interface Component {void operation();
}/*** author 小白程序员* Classname ConcreteComponent* Description 基础组件库的实现* date 2023/8/17 17:27*/
public class ConcreteComponent implements Component{Overridepublic void operation() {System.out.println(基础组件操作);}
}/*** author 小白程序员* Classname Decorator* Description 装饰者抽象类* date 2023/8/17 17:28*/
public abstract class Decorator implements Component{protected Component component;public Decorator(Component component){this.component component;}Overridepublic void operation() {component.operation();}
}/*** author 小白程序员* Classname ConcreteDecoratorA* Description 具体的装饰者类A* date 2023/8/17 17:32*/
public class ConcreteDecoratorA extends Decorator{public ConcreteDecoratorA(Component component) {super(component);}Overridepublic void operation(){System.out.println(添加额外行为A);super.operation();System.out.println(添加额外行为A);}}/*** author 小白程序员* Classname ConcreteDecoratorB* Description 具体的装饰者类B* date 2023/8/17 17:34*/
public class ConcreteDecoratorB extends Decorator{public ConcreteDecoratorB(Component component) {super(component);}Overridepublic void operation(){System.out.println(添加额外行为B);super.operation();System.out.println(添加额外行为B);}
}/*** author 小白程序员* Classname Main* Description 测试* date 2023/8/17 17:36*/
public class Main {public static void main(String[] args) {Component component new ConcreteComponent();component.operation();System.out.println(------------------------);Component concreteDecoratorA new ConcreteDecoratorA(component);concreteDecoratorA.operation();System.out.println(------------------------);Component concreteDecoratorB new ConcreteDecoratorB(component);concreteDecoratorB.operation();System.out.println(------------------------);Component concreteDecoratorAB new ConcreteDecoratorB(new ConcreteDecoratorA(component));concreteDecoratorAB.operation();}
} 在这个示例中ConcreteComponent是基础组件类Decorator是装饰者抽象类ConcreteDecoratorA和ConcreteDecoratorB是具体的装饰者类。我们可以通过创建不同的装饰者对象来动态地给基础组件添加额外的行为。
使用场景 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。 不能采用继承的情况主要有两类 第一类是系统中存在大量独立的扩展为支持每一种组合将产生大量的子类使得子类数目呈爆炸性增长 第二类是因为类定义不能继承如final类 在不影响其他对象的情况下以动态、透明的方式给单个对象添加职责。 当对象的功能要求可以动态地添加也可以再动态地撤销时。
❗静态代理和装饰者的区别
静态代理和装饰者模式的区别 相同点 都要实现与目标类相同的业务接口 在两个类中都要声明目标对象 都可以在不修改目标类的前提下增强目标方法 不同点 目的不同 装饰者是为了增强目标对象 静态代理是为了保护和隐藏目标对象 获取目标对象构建的地方不同 装饰者是由外界传递进来可以通过构造方法传递 静态代理是在代理类内部创建以此来隐藏目标对象
2.4桥接模式 它用于将抽象部分和实现部分分离使它们可以独立地变化。通俗来说桥接模式就像是搭建一座桥连接两个独立的部分。
桥接Bridge模式包含以下主要角色 抽象化Abstraction角色 定义抽象类并包含一个对实现化对象的引用。 扩展抽象化Refined Abstraction角色 是抽象化角色的子类实现父类中的业务方法并通过组合关系调用实现化角色中的业务方法。 实现化Implementor角色 定义实现化角色的接口供扩展抽象化角色调用。 具体实现化Concrete Implementor角色 给出实现化角色接口的具体实现。 假设有两个维度的类别比如形状Shape和颜色Color每个类别都有多个具体的实现类。在没有桥接模式的情况下我们可能需要为每个形状和颜色的组合创建一个具体的类这样会导致类的爆炸性增长。 而通过桥接模式我们可以将形状和颜色这两个维度进行解耦将它们分别作为独立的类层级结构。然后使用一个桥接接口Bridge来连接形状和颜色实现它们之间的关联。这样当我们需要新增一种形状或者颜色时只需要扩展对应的类层级即可不需要修改已有的代码。 举个例子假设我们有形状接口 Shape 和颜色接口 Color它们分别有多个具体的实现类比如圆形、正方形、红色、蓝色等。通过桥接模式我们可以定义一个桥接接口 DrawAPI它有一个方法 drawShape然后在具体的形状类中调用 DrawAPI 的 drawShape 方法来绘制形状。这样我们可以根据需要随意组合形状和颜色而不需要为每一种组合都创建一个具体的类。
/*** author 小白程序员* Classname Shape* Description 形状接口* date 2023/8/18 15:38*/
public interface Shape {void draw();
}/*** author 小白程序员* Classname Color* Description 颜色接口* date 2023/8/18 15:40*/
public interface Color {void fill();
}/*** author 小白程序员* Classname Circle* Description 形状接口具体实现类* date 2023/8/18 15:41*/
public class Circle implements Shape{private Color color;public Circle(Color color){this.color color;}Overridepublic void draw() {System.out.println(绘制圆形);color.fill();}
}/*** author 小白程序员* Classname Red* Description 颜色具体实现类* date 2023/8/18 15:43*/
public class Red implements Color{Overridepublic void fill() {System.out.println(填充红色);}
}/*** author 小白程序员* Classname BridgePatternDemo* Description 测试* date 2023/8/18 15:44*/
public class BridgePatternDemo {public static void main(String[] args) {Shape redCricle new Circle(new Red());redCricle.draw();}
} 通过桥接模式我们可以灵活地组合不同的形状和颜色而不需要为每一种组合都创建一个具体的类。
好处 桥接模式提高了系统的可扩充性在两个变化维度中任意扩展一个维度都不需要修改原有系统。 实现细节对客户透明
使用场景 当一个类存在两个独立变化的维度且这两个维度都需要进行扩展时。 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系通过桥接模式可以使它们在抽象层建立一个关联关系。
2.5外观模式 它提供了一个简单的接口隐藏了底层复杂系统的复杂性使得使用者可以更方便地与系统进行交互。通俗来说外观模式就像是一个门面将系统的复杂性隐藏在背后对外提供一个简单的接口。
外观Facade模式包含以下主要角色 外观Facade角色为多个子系统对外提供一个共同的接口。 子系统Sub System角色实现系统的部分功能客户可以通过外观角色访问它。 假设你要使用一个复杂的系统这个系统由多个子系统组成每个子系统都有很多复杂的类和方法。如果直接与每个子系统进行交互那么会变得非常繁琐和复杂。而通过外观模式我们可以定义一个外观类Facade它封装了底层子系统的复杂操作并提供一个简单的接口给使用者。 举个例子假设你要使用一个电脑电脑内部有CPU、内存和硬盘等多个子系统。如果直接与每个子系统进行交互需要了解各个子系统的细节操作起来会很麻烦。而通过外观模式我们可以定义一个电脑外观类ComputerFacade它封装了启动电脑、关闭电脑等复杂操作并提供一个简单的接口给使用者比如 startComputer() 和 shutdownComputer()。
/*** author 小白程序员* Classname CPU* Description 子系统类* date 2023/8/18 16:05*/
public class CPU {public void start(){System.out.println(CPU启动);}public void shutdown(){System.out.println(CPU关闭);}
}/*** author 小白程序员* Classname Memory* Description 子系统类* date 2023/8/18 16:06*/
public class Memory {public void start(){System.out.println(内存启动);}public void shutdown(){System.out.println(内存关闭);}
}/*** author 小白程序员* Classname HardDrive* Description 子系统类* date 2023/8/18 16:07*/
public class HardDrive {public void start(){System.out.println(硬盘启动);}public void shutdown(){System.out.println(硬盘关闭);}
}/*** author 小白程序员* Classname ComputerFacade* Description 外观类* date 2023/8/18 16:08*/
public class ComputerFacade {private CPU cpu;private Memory memory;private HardDrive hardDrive;public ComputerFacade(){this.cpu new CPU();this.memory new Memory();this.hardDrive new HardDrive();}public void startComputer(){System.out.println(启动电脑);cpu.start();memory.start();hardDrive.start();System.out.println(电脑启动完成);}public void shutdownComputer(){System.out.println(关闭电脑);cpu.shutdown();memory.shutdown();hardDrive.shutdown();System.out.println(电脑关闭完成);}}/*** author 小白程序员* Classname FacadeOatternDemo* Description 测试* date 2023/8/18 16:13*/
public class FacadeOatternDemo {public static void main(String[] args) {ComputerFacade computer new ComputerFacade();computer.startComputer();System.out.println();computer.shutdownComputer();}
} 通过外观模式我们可以使用简单的接口 startComputer() 和 shutdownComputer() 来启动和关闭电脑而不需要了解底层子系统的复杂性。
好处 降低了子系统与客户端之间的耦合度使得子系统的变化不会影响调用它的客户类。 对客户屏蔽了子系统组件减少了客户处理的对象数目并使得子系统使用起来更加容易。
缺点 不符合开闭原则修改很麻烦 使用场景 对分层结构系统构建时使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。 当一个复杂系统的子系统很多时外观模式可以为系统设计一个简单的接口供外界访问。 当客户端与多个子系统之间存在很大的联系时引入外观模式可将它们分离从而提高子系统的独立性和可移植性。
2.6组合模式 组合模式允许我们将对象组合成树形结构来表示部分-整体的层次关系。通俗来讲组合模式就像是一个文件夹里面可以包含文件和其他文件夹形成了一个树状结构。
组合模式主要包含三种角色 抽象根节点Component定义系统各层次对象的共有方法和属性可以预先定义一些默认行为和属性。 树枝节点Composite定义树枝节点的行为存储子节点组合树枝节点和叶子节点形成一个树形结构。 叶子节点Leaf叶子节点对象其下再无分支是系统层次遍历的最小单位。 举个例子假设你要处理一个文件系统文件系统由文件和文件夹组成。文件夹可以包含文件和其他文件夹而文件不能再包含其他文件或文件夹。如果直接操作每个文件和文件夹会变得非常繁琐和复杂。而通过组合模式我们可以使用统一的方式来处理文件和文件夹将它们组织成一个树形结构。 在组合模式中我们定义一个抽象类或接口比如 Component它既可以代表文件也可以代表文件夹。然后我们实现具体的文件类比如 File和文件夹类比如 Folder。文件夹类中可以包含多个文件和文件夹从而形成了一个树状结构。
/*** author 小白程序员* Classname File* Description 文件类* date 2023/8/18 16:42*/
public class File extends Component{public File(String name) {super(name);}Overridepublic void display() {System.out.println(文件name);}
}/*** author 小白程序员* Classname Folder* Description 文件夹类* date 2023/8/18 16:44*/
public class Folder extends Component{private final ListComponent components;public Folder(String name) {super(name);components new ArrayList();}Overridepublic void display() {System.out.println(文件夹name);for (Component component : components) {component.display();}}public void add(Component component){components.add(component);}public void remove(Component component){components.remove(component);}
}/*** author 小白程序员* Classname Component* Description 抽象类* date 2023/8/18 16:39*/
public abstract class Component {protected String name;public Component(String name){this.name name;}public abstract void display();public void add(Component component){throw new UnsupportedOperationException();}public void remove(Component component){throw new UnsupportedOperationException();}
}/*** author 小白程序员* Classname CompositePatternDemo* Description 测试* date 2023/8/18 16:50*/
public class CompositePatternDemo {public static void main(String[] args) {Component file new File(file1.txt);Component file1 new File(file2.txt);Component folder1 new Folder(folder1);Component folder2 new Folder(folder2);folder1.add(file);folder2.add(file1);folder2.add(folder1);folder2.display();}
} 通过组合模式我们可以使用统一的方式来处理文件和文件夹无论是操作单个文件还是整个文件夹都可以使用相同的方式进行处理。
组合模式的分类 在使用组合模式时根据抽象构件类的定义形式我们可将组合模式分为透明组合模式和安全组合模式两种形式。 透明组合模式 透明组合模式中抽象根节点角色中声明了所有用于管理成员对象的方法比如在示例中 Component声明了 add、remove 方法这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。 透明组合模式的缺点是不够安全因为叶子对象和容器对象在本质上是有区别的叶子对象不可能有下一个层次的对象即不可能包含成员对象因此为其提供 add()、remove() 等方法是没有意义的这在编译阶段不会出错但在运行阶段如果调用这些方法可能会出错如果没有提供相应的错误处理代码 安全组合模式 在安全组合模式中在抽象构件角色中没有声明任何用于管理成员对象的方法而是在树枝节点 Folder类中声明并实现这些方法。安全组合模式的缺点是不够透明因为叶子构件和容器构件具有不同的方法且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义因此客户端不能完全针对抽象编程必须有区别地对待叶子构件和容器构件。
优点 组合模式可以清楚地定义分层次的复杂对象表示对象的全部或部分层次它让客户端忽略了层次的差异方便对整个层次结构进行控制。 客户端可以一致地使用一个组合结构或其中单个对象不必关心处理的是单个对象还是整个组合结构简化了客户端代码。 在组合模式中增加新的树枝节点和叶子节点都很方便无须对现有类库进行任何修改符合“开闭原则”。 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案通过叶子节点和树枝节点的递归组合可以形成复杂的树形结构但对树形结构的控制却非常简单。
应用场景 组合模式正是应树形结构而生所以组合模式的使用场景就是出现树形结构的地方。比如文件目录显示多级目录呈现等树形结构数据的操作。
2.7享元模式 享元模式它的目标是通过共享对象来减少内存使用和提高性能。在享元模式中将对象分为可共享的内部状态和不可共享的外部状态。 享元Flyweight 模式中存在以下两种状态 内部状态即不会随着环境的改变而改变的可共享部分。 外部状态指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态并将外部状态外部化。 享元模式的主要有以下角色 抽象享元角色Flyweight通常是一个接口或抽象类在抽象享元类中声明了具体享元类公共的方法这些方法可以向外界提供享元对象的内部数据内部状态同时也可以通过这些方法来设置外部数据外部状态。 具体享元Concrete Flyweight角色 它实现了抽象享元类称为享元对象在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类为每一个具体享元类提供唯一的享元对象。 非享元Unsharable Flyweight)角色 并不是所有的抽象享元类的子类都需要被共享不能被共享的子类可设计为非共享具体享元类当需要一个非共享具体享元类的对象时可以直接通过实例化创建。 享元工厂Flyweight Factory角色 负责创建和管理享元角色。当客户对象请求一个享元对象时享元工厂检査系统中是否存在符合要求的享元对象如果存在则提供给客户如果不存在的话则创建一个新的享元对象。 通俗地说想象一下你正在玩一个多人游戏每个玩家都有自己的角色。这些角色可能具有相同的外观、技能或者其他属性。如果每个角色都创建一个新的对象那么会占用大量的内存。而使用享元模式我们可以将相同的属性提取出来作为内部状态并在需要时共享这些内部状态从而节省内存。
/*** author 小白程序员* Classname Character* Description 游戏角色类* date 2023/8/18 20:45* 在这个例子中name 和 weapon 是角色的外部状态因为它们会随着每个角色的不同而变化。* 如果我们有很多角色但是有些角色具有相同的武器那么我们可以将武器作为内部状态进行共享。*/
Data
AllArgsConstructor
NoArgsConstructor
public class Character {private String name;private String weapon;
}/*** author 小白程序员* Classname CharacterFactory* Description 享元工厂类* date 2023/8/18 20:47* 在上述代码中CharacterFactory 是享元工厂类负责创建和管理角色对象。* 当需要一个角色时首先检查是否已经有该武器的角色存在如果存在则直接返回共享的角色对象否则创建一个新的角色对象并进行缓存。* 通过使用享元模式我们可以节省内存并且在需要时重复使用相同的属性。这对于拥有大量相似对象的场景非常有用例如游戏中的角色、图形编辑器中的图形等。*/
public class CharacterFactory {private MapString,Character characterMap new HashMap();public Character getCharacter(String weapon){if(!characterMap.containsKey(weapon)){Character character new Character(Default,weapon);characterMap.put(weapon,character);}return characterMap.get(weapon);}
}/*** author 小白程序员* Classname FlyweightPatternTest* Description 测试* date 2023/8/18 20:55*/
public class FlyweightPatternTest {public static void main(String[] args) {CharacterFactory characterFactory new CharacterFactory();//创建两个角色武器相同Character character1 characterFactory.getCharacter(Sword);Character character2 characterFactory.getCharacter(Sword);//创建另一个角色武器不同Character character3 characterFactory.getCharacter(Bow);//检查是否共享了相同的角色对象System.out.println(character1 character2);System.out.println(character1 character3);}} 在上述测试类中我们首先创建了一个 CharacterFactory 对象然后通过该工厂获取了几个角色对象。其中character1 和 character2 具有相同的武器 Sword而 character3 的武器是 Bow。 最后我们使用 运算符检查了这些角色对象的引用是否相同。根据享元模式的定义character1 和 character2 应该是同一个对象而 character1 和 character3 是不同的对象。
优点: 极大减少内存中相似或相同对象数量节约系统资源提供系统性能 享元模式中的外部状态相对独立且不影响内部状态
缺点 为了使对象可以共享需要将享元对象的部分状态外部化分离内部状态和外部状态使程序逻辑复杂
使用场景 一个系统有大量相同或者相似的对象造成内存的大量耗费。 对象的大部分状态都可以外部化可以将这些外部状态传入对象中。 在使用享元模式时需要维护一个存储享元对象的享元池而这需要耗费一定的系统资源因此应当在需要多次重复使用享元对象时才值得使用享元模式。 三、行为性模式
3.1模板方法模式 模板方法模式它定义了一个算法的骨架将某些步骤的实现延迟到子类中。通俗地说模板方法模式就像是制定了一个操作流程的模板其中一些具体的步骤由子类来实现。 举个例子假设我们要编写一个制作咖啡和茶的程序。这两种饮料的制作过程有一些共同的步骤例如煮水、冲泡、倒入杯子等但是具体的步骤和顺序可能会有所不同。 在模板方法模式中我们可以定义一个抽象类其中包含一个模板方法该方法定义了整个制作过程的骨架并调用了一系列的抽象方法这些抽象方法由子类来实现。
模板方法Template Method模式包含以下主要角色 抽象类Abstract Class负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。 模板方法定义了算法的骨架按某种顺序调用其包含的基本方法。 基本方法是实现算法各个步骤的方法是模板方法的组成部分。基本方法又可以分为三种 抽象方法(Abstract Method) 一个抽象方法由抽象类声明、由其具体子类实现。 具体方法(Concrete Method) 一个具体方法由一个抽象类或具体类声明并实现其子类可以进行覆盖也可以直接继承。 钩子方法(Hook Method) 在抽象类中已经实现包括用于判断的逻辑方法和需要子类重写的空方法两种。 一般钩子方法是用于判断的逻辑方法这类方法名一般为isXxx返回值类型为boolean类型。 具体子类Concrete Class实现抽象类中所定义的抽象方法和钩子方法它们是一个顶级逻辑的组成步骤。
/*** author 小白程序员* Classname Beverage* Description TODO干啥呢* date 2023/8/18 21:17* Beverage 是抽象类其中 prepareBeverage() 方法是模板方法定义了制作饮料的整个流程。* brew() 和 addCondiments() 是抽象方法由子类来实现具体的步骤。*/
public abstract class Beverage {public final void prepareRecipe(){boilWater();brew();pourInCup();addCondiments();}protected abstract void addCondiments();private void pourInCup() {System.out.println(Pouring into cup);}protected abstract void brew();private void boilWater() {System.out.println(Boiling water);}
}/*** author 小白程序员* Classname Coffee* Description Coffee类* date 2023/8/18 21:22*/
public class Coffee extends Beverage{Overrideprotected void addCondiments() {System.out.println(加糖);}Overrideprotected void brew() {System.out.println(冲泡咖啡);}
}/*** author 小白程序员* Classname BeverageTest* Description 测试* date 2023/8/18 21:24*/
public class BeverageTest {public static void main(String[] args) {Beverage coffee new Coffee();coffee.prepareRecipe();}
} 注意为防止恶意操作一般模板方法都加上 final 关键词。
优点 提高代码复用性 将相同部分的代码放在抽象的父类中而将不同的代码放入不同的子类中。 实现了反向控制 通过一个父类调用其子类的操作通过对子类的具体实现扩展不同的行为实现了反向控制 并符合“开闭原则”。
缺点 对每个不同的实现都需要定义一个子类这会导致类的个数增加系统更加庞大设计也更加抽象。 父类中的抽象方法由子类实现子类执行的结果会影响父类的结果这导致一种反向的控制结构它提高了代码阅读的难度。
使用场景 算法的整体步骤很固定但其中个别部分易变时这时候可以使用模板方法模式将容易变的部分抽象出来供子类实现。 需要通过子类来决定父类算法中某个步骤是否执行实现子类对父类的反向控制。
3.2策略模式 策略模式它允许在运行时根据不同的情况选择不同的算法或行为。通俗地说策略模式就像是在一个问题上有多个可选的解决方案我们可以根据具体的需求来选择使用哪种解决方案。
策略模式的主要角色如下 抽象策略Strategy类这是一个抽象角色通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。 具体策略Concrete Strategy类实现了抽象策略定义的接口提供具体的算法实现或行为。 环境Context类持有一个策略类的引用最终给客户端调用。 举个例子假设我们正在开发一个游戏其中有多种角色每个角色都有不同的攻击方式。我们可以使用策略模式来实现这个功能。
/*** author 小白程序员* Classname Character* Description 角色类* date 2023/8/18 21:42*/
public abstract class Character {public abstract void attack();
}/*** author 小白程序员* Classname Knight* Description 角色具体实现类* date 2023/8/18 21:43*/
public class Knight extends Character{Overridepublic void attack() {System.out.println(Knight attacks with a sword);}
}/*** author 小白程序员* Classname Mage* Description 角色具体实现类* date 2023/8/18 21:47*/
public class Mage extends Character{Overridepublic void attack() {System.out.println(法师释放大法术);}
}/*** author 小白程序员* Classname Game* Description Game 类通过 setCharacter() 方法设置角色对象并通过 performAttack() 方法执行角色的攻击动作。* date 2023/8/18 21:48* Game 类通过 setCharacter() 方法设置角色对象并通过 performAttack() 方法执行角色的攻击动作。*/
public class Game {private Character character;public void setCharacter(Character character) {this.character character;}public void performAttack(){character.attack();}
}/*** author 小白程序员* Classname StrategyPatternTest* Description 测试* date 2023/8/18 21:59*/
public class StrategyPatternTest {public static void main(String[] args) {Game game new Game();Character knight new Knight();game.setCharacter(knight);game.performAttack();Character mage new Mage();game.setCharacter(mage);game.performAttack();}
}
优点 策略类之间可以自由切换 由于策略类都实现同一个接口所以使它们之间可以自由切换。 易于扩展 增加一个新的策略只需要添加一个具体的策略类即可基本不需要改变原有的代码符合“开闭原则“ 避免使用多重条件选择语句if else充分体现面向对象设计思想。
缺点 客户端必须知道所有的策略类并自行决定使用哪一个策略类。 策略模式将造成产生很多策略类可以通过使用享元模式在一定程度上减少对象的数量。 使用场景 一个系统需要动态地在几种算法中选择一种时可将每个算法封装到策略类中。 一个类定义了多种行为并且这些行为在这个类的操作中以多个条件语句的形式出现可将每个条件分支移入它们各自的策略类中以代替这些条件语句。 系统中各算法彼此完全独立且要求对客户隐藏具体算法的实现细节时。 系统要求使用算法的客户不应该知道其操作的数据时可使用策略模式来隐藏与算法相关的数据结构。 多个类只区别在表现行为不同可以使用策略模式在运行时动态选择具体要执行的行为。
3.3命令模式 命令模式它允许将请求封装成一个对象从而使得可以用不同的请求对客户端进行参数化。这样可以将请求的发送者和接收者解耦使得系统更加灵活。 通俗来说命令模式就像是我们在餐厅点餐一样。顾客客户端通过服务员调用者来下订单命令然后服务员将订单交给厨师接收者厨师根据订单进行烹饪执行命令。这样顾客不需要直接与厨师交流而是通过服务员来传达自己的需求。
在命令模式中有以下几个关键角色
命令Command定义了执行操作的接口包含了执行命令的方法。具体命令Concrete Command实现了命令接口持有接收者并将具体的操作委托给接收者执行。接收者Receiver负责具体执行命令所要求的操作。调用者Invoker负责调用命令对象并触发命令执行。客户端Client创建命令对象并设置其接收者将命令对象传递给调用者进行调用。 通过使用命令模式我们可以将请求的发送者和接收者解耦使得系统更加灵活。例如我们可以轻松地添加新的命令而无需修改现有的代码。同时命令模式还支持撤销、重做等操作提供了更多的灵活性和可扩展性。
/*** author 小白程序员* Classname Command* Description 命令接口* date 2023/8/20 14:33*/
public interface Command {void execute();
}/*** author 小白程序员* Classname Light* Description 接收类* date 2023/8/20 14:35*/
public class Light {public void turnOn(){System.out.println(Light is on);}public void turnOff(){System.out.println(Light is off);}
}/*** author 小白程序员* Classname LightOnCommand* Description 命令类* date 2023/8/20 14:34*/
public class LightOnCommand implements Command{private Light light;public LightOnCommand(Light light) {this.light light;}Overridepublic void execute() {light.turnOn();}
}/*** author 小白程序员* Classname LightOffCommand* Description 命令类* date 2023/8/20 14:37*/
public class LightOffCommand implements Command{private Light light;public LightOffCommand(Light light) {this.light light;}Overridepublic void execute() {light.turnOff();}
}/*** author 小白程序员* Classname RemoteControl* Description 调用者类* date 2023/8/20 14:38*/
public class RemoteControl {private Command command;public void setCommand(Command command){this.command command;}public void pressButton(){command.execute();}
}/*** author 小白程序员* Classname Client* Description 测试* date 2023/8/20 14:39*/
public class Client {public static void main(String[] args) {Light light new Light();LightOnCommand lightOnCommand new LightOnCommand(light);LightOffCommand lightOffCommand new LightOffCommand(light);RemoteControl remoteControl new RemoteControl();remoteControl.setCommand(lightOnCommand);remoteControl.pressButton();remoteControl.setCommand(lightOffCommand);remoteControl.pressButton();}
} 通过这个例子我们可以看到命令模式的使用方式。客户端创建具体的命令对象并将其传递给调用者。调用者负责调用命令对象的 execute() 方法来触发命令的执行而不需要直接与接收者交互。
优点 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类它满足“开闭原则”对扩展比较灵活。 可以实现宏命令。命令模式可以与组合模式结合将多个命令装配成一个组合命令即宏命令。 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合实现命令的撤销与恢复。
缺点 使用命令模式可能会导致某些系统有过多的具体命令类。 系统结构更加复杂。
使用场景 系统需要将请求调用者和请求接收者解耦使得调用者和接收者不直接交互。 系统需要在不同的时间指定请求、将请求排队和执行请求。 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
3.4责任链模式 责任链模式它允许将请求沿着处理链进行传递直到有一个处理者能够处理该请求为止。每个处理者都可以决定是否将请求传递给下一个处理者。 通俗来说责任链模式就像是在工厂生产线上的流水线作业一样。产品从流水线的第一个工人开始处理如果他能够完成工作则将产品交给下一个工人继续处理如果他无法完成工作则将产品交给下一个工人处理。这样产品会依次经过多个工人的处理直到最后一个工人完成工作。
在责任链模式中有以下几个关键角色
抽象处理者Handler定义了处理请求的接口并持有下一个处理者的引用。具体处理者Concrete Handler实现了处理请求的方法并决定是否将请求传递给下一个处理者。 客户类Client角色创建处理链并向链头的具体处理者对象提交请求它不关心处理细节和请求的传递过程。
首先我们定义抽象处理者接口 Handler
public interface Handler {void setNextHandler(Handler nextHandler);void handleRequest(Request request);
}
然后创建具体的处理者类 ConcreteHandlerA 和 ConcreteHandlerB它们实现了 Handler 接口
public class ConcreteHandlerA implements Handler {private Handler nextHandler;Overridepublic void setNextHandler(Handler nextHandler) {this.nextHandler nextHandler;}Overridepublic void handleRequest(Request request) {if (request.getType().equals(TypeA)) {System.out.println(ConcreteHandlerA 处理了请求);} else if (nextHandler ! null) {nextHandler.handleRequest(request);} else {System.out.println(没有合适的处理者);}}
}public class ConcreteHandlerB implements Handler {private Handler nextHandler;Overridepublic void setNextHandler(Handler nextHandler) {this.nextHandler nextHandler;}Overridepublic void handleRequest(Request request) {if (request.getType().equals(TypeB)) {System.out.println(ConcreteHandlerB 处理了请求);} else if (nextHandler ! null) {nextHandler.handleRequest(request);} else {System.out.println(没有合适的处理者);}}
} 接着我们创建请求类 Request用于封装请求的信息
public class Request {private String type;public Request(String type) {this.type type;}public String getType() {return type;}
} 最后我们可以在客户端中使用这些类来测试责任链模式
public class Client {public static void main(String[] args) {Handler handlerA new ConcreteHandlerA();Handler handlerB new ConcreteHandlerB();handlerA.setNextHandler(handlerB);Request request1 new Request(TypeA);handlerA.handleRequest(request1);Request request2 new Request(TypeB);handlerA.handleRequest(request2);Request request3 new Request(TypeC);handlerA.handleRequest(request3);}
}
运行上述代码输出结果为
ConcreteHandlerA 处理了请求ConcreteHandlerB 处理了请求没有合适的处理者 通过这个例子我们可以看到责任链模式的使用方式。客户端创建具体的处理者对象并设置它们的下一个处理者。当请求发生时首先由第一个处理者进行处理如果无法处理则将请求传递给下一个处理者直到找到能够处理该请求的处理者为止。
优点 降低了对象之间的耦合度 该模式降低了请求发送者和接收者的耦合度。 增强了系统的可扩展性 可以根据需要增加新的请求处理类满足开闭原则。 增强了给对象指派职责的灵活性 当工作流程发生变化可以动态地改变链内的成员或者修改它们的次序也可动态地新增或者删除责任。 责任链简化了对象之间的连接 一个对象只需保持一个指向其后继者的引用不需保持其他所有处理者的引用这避免了使用众多的 if 或者 if···else 语句。 责任分担 每个类只需要处理自己该处理的工作不能处理的传递给下一个对象完成明确各类的责任范围符合类的单一职责原则。
缺点 不能保证每个请求一定被处理。由于一个请求没有明确的接收者所以不能保证它一定会被处理该请求可能一直传到链的末端都得不到处理。 对比较长的职责链请求的处理可能涉及多个处理对象系统性能将受到一定影响。 职责链建立的合理性要靠客户端来保证增加了客户端的复杂性可能会由于职责链的错误设置而导致系统出错如可能会造成循环调用。
3.5状态模式 状态模式它允许对象在内部状态发生改变时改变其行为。换句话说状态模式可以让一个对象在不同的状态下表现出不同的行为。 想象一下你正在玩一个游戏你的角色有不同的状态比如正常状态、受伤状态和死亡状态。在不同的状态下你的角色可能会有不同的行为比如移动、攻击或者无法进行任何操作。
在状态模式中有以下几个关键角色
环境类Context维护一个对具体状态对象的引用并将客户端的请求委托给当前状态对象来处理。抽象状态类State定义了一个接口用于封装与环境类的特定状态相关的行为。具体状态类Concrete State实现了抽象状态类的接口具体实现了与该状态相关的行为。 假设我们有一个电梯系统其中包含电梯和多个楼层。电梯有不同的状态比如停止状态、运行状态和故障状态。在不同的状态下电梯可能会有不同的行为比如开门、关门、上升或下降。
首先我们定义一个抽象状态类 State它包含了电梯可能的行为
public interface State {void open();void close();void move();
}
然后我们创建具体的状态类 StopState、RunState 和 FaultState它们分别实现了 State 接口
public class StopState implements State {Overridepublic void open() {System.out.println(电梯门已打开);}Overridepublic void close() {System.out.println(电梯门已关闭);}Overridepublic void move() {System.out.println(电梯开始运行);}
}public class RunState implements State {Overridepublic void open() {System.out.println(电梯正在运行中无法打开门);}Overridepublic void close() {System.out.println(电梯门已关闭);}Overridepublic void move() {System.out.println(电梯继续运行);}
}public class FaultState implements State {Overridepublic void open() {System.out.println(电梯发生故障无法打开门);}Overridepublic void close() {System.out.println(电梯发生故障无法关闭门);}Overridepublic void move() {System.out.println(电梯发生故障无法移动);}
}
接下来我们创建环境类 Elevator它维护了当前的状态并将请求委托给当前状态对象来处理
public class Elevator {private State currentState;public Elevator() {// 初始状态为停止状态currentState new StopState();}public void setState(State state) {currentState state;}public void open() {currentState.open();}public void close() {currentState.close();}public void move() {currentState.move();}
} 最后我们可以在客户端中使用这些类来测试状态模式
public class Client {public static void main(String[] args) {Elevator elevator new Elevator();elevator.open(); // 输出电梯门已打开elevator.close(); // 输出电梯门已关闭elevator.move(); // 输出电梯开始运行elevator.setState(new FaultState());elevator.open(); // 输出电梯发生故障无法打开门elevator.close(); // 输出电梯发生故障无法关闭门elevator.move(); // 输出电梯发生故障无法移动}
} 通过这个例子我们可以看到状态模式的使用方式。在环境类中维护一个对具体状态对象的引用并将客户端的请求委托给当前状态对象来处理。不同的状态类实现了相应的行为从而使得环境类的行为随着状态的改变而改变。
优点 将所有与某个状态有关的行为放到一个类中并且可以方便地增加新的状态只需要改变对象状态即可改变对象的行为。 允许状态转换逻辑与状态对象合成一体而不是某一个巨大的条件语句块。
缺点 状态模式的使用必然会增加系统类和对象的个数。 状态模式的结构与实现都较为复杂如果使用不当将导致程序结构和代码的混乱。 状态模式对开闭原则的支持并不太好。
使用场景 当一个对象的行为取决于它的状态并且它必须在运行时根据状态改变它的行为时就可以考虑使用状态模式。 一个操作中含有庞大的分支结构并且这些分支决定于对象的状态时。
3.6观察者模式 观察者模式是一种行为设计模式它定义了对象之间的一对多依赖关系当一个对象的状态发生改变时所有依赖它的对象都会得到通知并自动更新。 想象一下你正在订阅一个新闻发布系统你和其他人都是这个系统的观察者。当有新的新闻发布时系统会自动通知所有的观察者并更新他们的新闻内容。
在观察者模式中有以下几个关键角色
主题Subject也称为被观察者或可观察对象它维护了一个观察者列表并提供了添加、删除和通知观察者的方法。观察者Observer定义了一个接口用于接收主题的通知并进行相应的更新操作。具体主题Concrete Subject实现了主题接口维护了观察者列表并在状态发生改变时通知观察者。具体观察者Concrete Observer实现了观察者接口接收到主题的通知后进行相应的更新操作。 假设我们有一个气象站它可以实时获取天气信息并将天气信息通知给订阅了该气象站的观察者。观察者可以是手机应用程序、网站或者其他设备。
首先我们定义一个主题接口 Subject其中包含了添加、删除和通知观察者的方法
public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers();
}
然后我们创建一个具体主题类 WeatherStation它实现了主题接口并维护了观察者列表
public class WeatherStation implements Subject {private ListObserver observers;private String weather;public WeatherStation() {observers new ArrayList();}Overridepublic void registerObserver(Observer observer) {observers.add(observer);}Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(weather);}}public void setWeather(String weather) {this.weather weather;notifyObservers();}
}
接下来我们定义一个观察者接口 Observer其中包含了更新操作的方法
public interface Observer {void update(String weather);
}
然后我们创建一个具体观察者类 PhoneApp它实现了观察者接口并在收到通知后进行相应的更新操作
public class PhoneApp implements Observer {Overridepublic void update(String weather) {System.out.println(手机应用程序收到天气更新 weather);}
} 最后我们可以在客户端中使用这些类来测试观察者模式
public class Client {public static void main(String[] args) {WeatherStation weatherStation new WeatherStation();PhoneApp phoneApp new PhoneApp();weatherStation.registerObserver(phoneApp);weatherStation.setWeather(晴天); // 输出手机应用程序收到天气更新晴天weatherStation.removeObserver(phoneApp);weatherStation.setWeather(下雨); // 无输出}
} 通过这个例子我们可以看到观察者模式的使用方式。主题维护了一个观察者列表并在状态发生改变时通知观察者。观察者实现了相应的接口在收到通知后进行相应的更新操作。
优点 降低了目标与观察者之间的耦合关系两者之间是抽象耦合关系。 被观察者发送通知所有注册的观察者都会收到信息【可以实现广播机制】
缺点 如果观察者非常多的话那么所有的观察者收到被观察者发送的通知会耗时 如果被观察者有循环依赖的话那么被观察者发送通知会使观察者循环调用会导致系统崩溃
使用场景 对象间存在一对多关系一个对象的状态发生改变会影响其他对象。 当一个抽象模型有两个方面其中一个方面依赖于另一方面时。
3.7中介者模式 中介者模式它通过引入一个中介者对象来解耦多个对象之间的交互。在中介者模式中多个对象不再直接相互通信而是通过中介者进行通信。 想象一下你和你的朋友们正在参加一个团队项目。在开始项目之前你们需要进行沟通和协调。传统的方式是每个人都与其他人直接交流但这样会导致沟通混乱和冲突。于是你们决定任命一个项目经理作为中介者他负责协调大家的工作、解决问题并传达信息。
在中介者模式中有以下几个关键角色
中介者Mediator定义了一个接口用于各个同事对象之间的通信。具体中介者Concrete Mediator实现了中介者接口负责协调各个同事对象之间的通信。同事对象Colleague定义了一个接口用于与中介者进行通信。具体同事对象Concrete Colleague实现了同事接口与其他同事对象通过中介者进行通信。 假设我们有一个聊天室应用程序用户可以在聊天室中发送消息并与其他用户进行交流。在这个应用程序中每个用户都是一个同事对象而聊天室就是中介者。
首先我们定义一个中介者接口 ChatRoom其中包含了发送消息的方法
public interface ChatRoom {void sendMessage(String message, User user);
}
然后我们创建一个具体中介者类 ChatRoomImpl它实现了中介者接口并负责协调各个用户之间的通信
public class ChatRoomImpl implements ChatRoom {Overridepublic void sendMessage(String message, User user) {System.out.println(new Date().toString() [ user.getName() ] : message);}
} 接下来我们定义一个同事接口 User其中包含了发送消息和接收消息的方法
public interface User {void sendMessage(String message);void receiveMessage(String message);String getName();
}
然后我们创建一个具体同事类 UserImpl它实现了同事接口并在发送消息时通过中介者发送消息给其他用户在接收消息时将消息打印出来
public class UserImpl implements User {private String name;private ChatRoom chatRoom;public UserImpl(String name, ChatRoom chatRoom) {this.name name;this.chatRoom chatRoom;}Overridepublic void sendMessage(String message) {chatRoom.sendMessage(message, this);}Overridepublic void receiveMessage(String message) {System.out.println(name received message: message);}Overridepublic String getName() {return name;}
}
最后我们可以在客户端中使用这些类来测试中介者模式
public class Client {public static void main(String[] args) {ChatRoom chatRoom new ChatRoomImpl();User user1 new UserImpl(User1, chatRoom);User user2 new UserImpl(User2, chatRoom);user1.sendMessage(Hello, User2!); // 输出[User1] : Hello, User2!user2.sendMessage(Hi, User1!); // 输出[User2] : Hi, User1!}
} 通过这个例子我们可以看到中介者模式的使用方式。同事对象之间不再直接通信而是通过中介者进行通信。中介者负责协调各个同事对象之间的交互并将消息传递给目标用户。
优点 ①松散耦合 中介者模式通过把多个同事对象之间的交互封装到中介者对象里面从而使得同事对象之间松散耦合基本上可以做到互补依赖。这样一来同事对象就可以独立地变化和复用而不再像以前那样“牵一处而动全身”了。 ②集中控制交互 多个同事对象的交互被封装在中介者对象里面集中管理使得这些交互行为发生变化的时候只需要修改中介者对象就可以了当然如果是已经做好的系统那么就扩展中介者对象而各个同事类不需要做修改。 ③一对多关联转变为一对一的关联 没有使用中介者模式的时候同事对象之间的关系通常是一对多的引入中介者对象以后中介者对象和同事对象的关系通常变成双向的一对一这会让对象的关系更容易理解和实现。
缺点 当同事类太多时中介者的职责将很大它会变得复杂而庞大以至于系统难以维护。
使用场景 系统中对象之间存在复杂的引用关系系统结构混乱且难以理解。 当想创建一个运行于多个类之间的对象又不想生成新的子类时。
3.8迭代器模式 迭代器模式它提供了一种顺序访问聚合对象中各个元素的方法而不需要暴露其内部结构。 想象一下你去购物超市买东西。超市里有很多货架每个货架上都摆放着不同的商品。你想要遍历这些商品并逐个查看它们的信息但你不想知道货架是如何组织和排列的。 在这种情况下超市可以被视为一个聚合对象每个货架上的商品可以被视为聚合对象中的元素。而你则可以使用迭代器来遍历这些商品无需了解超市内部的具体实现。
在迭代器模式中有以下几个关键角色
迭代器Iterator定义了访问和遍历聚合对象元素的接口。具体迭代器Concrete Iterator实现了迭代器接口负责具体的遍历操作。聚合对象Aggregate定义了创建迭代器对象的接口。具体聚合对象Concrete Aggregate实现了聚合对象接口负责创建具体迭代器对象。 假设我们有一个名为 Book 的类表示一本书。我们还有一个 BookShelf 类它表示一个书架可以存放多本书。 首先我们定义一个迭代器接口 Iterator其中包含了访问和遍历书架上书籍的方法
public interface Iterator {boolean hasNext();Object next();
}
然后我们创建一个具体迭代器类 BookShelfIterator它实现了迭代器接口并负责具体的遍历操作
public class BookShelfIterator implements Iterator {private BookShelf bookShelf;private int index;public BookShelfIterator(BookShelf bookShelf) {this.bookShelf bookShelf;this.index 0;}Overridepublic boolean hasNext() {return index bookShelf.getLength();}Overridepublic Object next() {Book book bookShelf.getBookAt(index);index;return book;}
} 接下来我们定义一个聚合对象接口 Aggregate其中包含了创建迭代器对象的方法
public interface Aggregate {Iterator createIterator();
}
然后我们创建一个具体聚合对象类 BookShelf它实现了聚合对象接口并负责创建具体迭代器对象
public class BookShelf implements Aggregate {private Book[] books;private int last 0;public BookShelf(int maxSize) {this.books new Book[maxSize];}public Book getBookAt(int index) {return books[index];}public void addBook(Book book) {if (last books.length) {books[last] book;last;}}public int getLength() {return last;}Overridepublic Iterator createIterator() {return new BookShelfIterator(this);}
}
最后我们可以在客户端中使用这些类来测试迭代器模式
public class Client {public static void main(String[] args) {BookShelf bookShelf new BookShelf(3);bookShelf.addBook(new Book(Design Patterns));bookShelf.addBook(new Book(Clean Code));bookShelf.addBook(new Book(Refactoring));Iterator iterator bookShelf.createIterator();while (iterator.hasNext()) {Book book (Book) iterator.next();System.out.println(book.getName());}}
} 通过这个例子我们可以看到迭代器模式的使用方式。迭代器提供了一种简单而统一的方式来遍历聚合对象中的元素无需了解其内部结构。
优点 它支持以不同的方式遍历一个聚合对象在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法我们也可以自己定义迭代器的子类以支持新的遍历方式。 迭代器简化了聚合类。由于引入了迭代器在原有的聚合对象中不需要再自行提供数据遍历等方法这样可以简化聚合类的设计。 在迭代器模式中由于引入了抽象层增加新的聚合类和迭代器类都很方便无须修改原有代码满足 “开闭原则” 的要求。
缺点
增加了类的个数这在一定程度上增加了系统的复杂性。
使用场景 当需要为聚合对象提供多种遍历方式时。 当需要为遍历不同的聚合结构提供一个统一的接口时。 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。 注意 当我们在使用JAVA开发的时候想使用迭代器模式的话只要让我们自己定义的容器类实现java.util.Iterable并实现其中的iterator()方法使其返回一个 java.util.Iterator 的实现类就可以了。 3.9访问者模式 访问者模式它允许你在不改变已有对象结构的前提下定义新的操作。 想象一下你去参观一个博物馆。博物馆里有很多展品每个展品都有不同的特点和价值。你可以自由地在博物馆中游览并对每个展品进行不同的观察和评价比如拍照、记录信息等。 在这种情况下博物馆可以被视为一个对象结构每个展品可以被视为该对象结构中的元素。而你则可以作为一个访问者在不改变博物馆内部结构的情况下对每个展品进行不同的操作。
在访问者模式中有以下几个关键角色
访问者Visitor定义了对每个元素进行访问的方法可以根据需要定义多个不同的访问者。具体访问者Concrete Visitor实现了访问者接口负责具体的访问操作。元素Element定义了接受访问者访问的方法。具体元素Concrete Element实现了元素接口负责具体的接受访问者访问的操作。对象结构Object Structure包含了一组元素并提供了遍历这些元素的方法。
假设我们有一个名为 Element 的接口其中包含了接受访问者访问的方法
public interface Element {void accept(Visitor visitor);
}
然后我们创建一个具体元素类 ConcreteElementA 和 ConcreteElementB它们实现了元素接口并负责具体的接受访问者访问的操作
public class ConcreteElementA implements Element {Overridepublic void accept(Visitor visitor) {visitor.visit(this);}public String operationA() {return 具体元素A的操作;}
}public class ConcreteElementB implements Element {Overridepublic void accept(Visitor visitor) {visitor.visit(this);}public String operationB() {return 具体元素B的操作;}
}
接下来我们定义一个访问者接口 Visitor其中包含了对每个元素进行访问的方法
public interface Visitor {void visit(ConcreteElementA elementA);void visit(ConcreteElementB elementB);
}
然后我们创建一个具体访问者类 ConcreteVisitor它实现了访问者接口并负责具体的访问操作
public class ConcreteVisitor implements Visitor {Overridepublic void visit(ConcreteElementA elementA) {System.out.println(elementA.operationA());}Overridepublic void visit(ConcreteElementB elementB) {System.out.println(elementB.operationB());}
}
最后我们可以在客户端中使用这些类来测试访问者模式
public class Client {public static void main(String[] args) {Element elementA new ConcreteElementA();Element elementB new ConcreteElementB();Visitor visitor new ConcreteVisitor();elementA.accept(visitor);elementB.accept(visitor);}
} 通过这个例子我们可以看到访问者模式的使用方式。访问者模式允许你定义新的操作而无需改变已有对象结构的代码。
优点 扩展性好 在不修改对象结构中的元素的情况下为对象结构中的元素添加新的功能。 复用性好 通过访问者来定义整个对象结构通用的功能从而提高复用程度。 分离无关行为 通过访问者来分离无关的行为把相关的行为封装在一起构成一个访问者这样每一个访问者的功能都比较单一。
缺点 对象结构变化很困难 在访问者模式中每增加一个新的元素类都要在每一个具体访问者类中增加相应的具体操作这违背了“开闭原则”。 违反了依赖倒置原则 访问者模式依赖了具体类而没有依赖抽象类。
使用场景 对象结构相对稳定但其操作算法经常变化的程序。 对象结构中的对象需要提供多种不同且不相关的操作而且要避免让这些操作的变化影响对象的结构。
扩展 访问者模式用到了一种双分派的技术。
1分派 变量被声明时的类型叫做变量的静态类型有些人又把静态类型叫做明显类型而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map new HashMap() map变量的静态类型是 Map 实际类型是 HashMap 。根据对象的类型而对方法进行的选择就是分派(Dispatch)分派(Dispatch)又分为两种即静态分派和动态分派。 静态分派(Static Dispatch) 发生在编译时期分派根据静态类型信息发生。静态分派对于我们来说并不陌生方法重载就是静态分派。 动态分派(Dynamic Dispatch) 发生在运行时期动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。
2动态分派
通过方法的重写支持动态分派。
public class Animal {public void execute() {System.out.println(Animal);}}public class Dog extends Animal {Overridepublic void execute() {System.out.println(dog);}}public class Cat extends Animal {Overridepublic void execute() {System.out.println(cat);}}public class Client {public static void main(String[] args) {Animal a new Dog();a.execute();Animal a1 new Cat();a1.execute();}}
上面代码的结果大家应该直接可以说出来这不就是多态吗运行执行的是子类中的方法。 Java编译器在编译时期并不总是知道哪些代码会被执行因为编译器仅仅知道对象的静态类型而不知道对象的真实类型而方法的调用则是根据对象的真实类型而不是静态类型。
3静态分派
通过方法重载支持静态分派。
public class Animal {}public class Dog extends Animal {}public class Cat extends Animal {}public class Execute {public void execute(Animal a) {System.out.println(Animal);}public void execute(Dog d) {System.out.println(dog);}public void execute(Cat c) {System.out.println(cat);}}public class Client {public static void main(String[] args) {Animal a new Animal();Animal a1 new Dog();Animal a2 new Cat();Execute exe new Execute();exe.execute(a);exe.execute(a1);exe.execute(a2);}}
运行结果 这个结果可能出乎一些人的意料了为什么呢 重载方法的分派是根据静态类型进行的这个分派过程在编译时期就完成了。
4双分派 所谓双分派技术就是在选择一个方法的时候不仅仅要根据消息接收者receiver的运行时区别还要根据参数的运行时区别。 public class Animal {public void accept(Execute exe) {exe.execute(this);}}public class Dog extends Animal {public void accept(Execute exe) {exe.execute(this);}}public class Cat extends Animal {public void accept(Execute exe) {exe.execute(this);}}public class Execute {public void execute(Animal a) {System.out.println(animal);}public void execute(Dog d) {System.out.println(dog);}public void execute(Cat c) {System.out.println(cat);}}public class Client {public static void main(String[] args) {Animal a new Animal();Animal d new Dog();Animal c new Cat();Execute exe new Execute();a.accept(exe);d.accept(exe);c.accept(exe);}} 在上面代码中客户端将Execute对象做为参数传递给Animal类型的变量调用的方法这里完成第一次分派这里是方法重写所以是动态分派也就是执行实际类型中的方法同时也将自己this作为参数传递进去这里就完成了第二次分派这里的Execute类中有多个重载的方法而传递进行的是this就是具体的实际类型的对象。 说到这里我们已经明白双分派是怎么回事了但是它有什么效果呢就是可以实现方法的动态绑定我们可以对上面的程序进行修改。
运行结果如下 双分派实现动态绑定的本质就是在重载方法委派的前面加上了继承体系中覆盖的环节由于覆盖是动态的所以重载就是动态的了。
3.10备忘录模式 备忘录模式它允许你在不破坏封装性的前提下捕获一个对象的内部状态并在需要时恢复到之前的状态。 想象一下你在写一个文档编辑器时你可以随时对文档进行修改和保存。但是有时候你可能会误操作或者需要撤销之前的修改。这时候备忘录模式就能派上用场了。
在备忘录模式中有以下几个关键角色
发起人Originator负责创建一个备忘录并记录当前状态。备忘录Memento存储发起人的内部状态。管理者Caretaker负责保存备忘录并在需要时将其恢复给发起人。 备忘录有两个等效的接口 窄接口管理者(Caretaker)对象和其他发起人对象之外的任何对象看到的是备忘录的窄接口(narror Interface)这个窄接口只允许他把备忘录对象传给其他的对象。 宽接口与管理者看到的窄接口相反发起人对象可以看到一个宽接口(wide Interface)这个宽接口允许它读取所有的数据以便根据这些数据恢复这个发起人对象的内部状态。 游戏中的某个场景一游戏角色有生命力、攻击力、防御力等数据在打Boss前和后一定会不一样的我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。
要实现上述案例有两种方式 “白箱”备忘录模式 “黑箱”备忘录模式
“白箱”备忘录模式
//游戏角色类
public class GameRole {private int vit; //生命力private int atk; //攻击力private int def; //防御力//初始化状态public void initState() {this.vit 100;this.atk 100;this.def 100;}//战斗public void fight() {this.vit 0;this.atk 0;this.def 0;}//保存角色状态public RoleStateMemento saveState() {return new RoleStateMemento(vit, atk, def);}//回复角色状态public void recoverState(RoleStateMemento roleStateMemento) {this.vit roleStateMemento.getVit();this.atk roleStateMemento.getAtk();this.def roleStateMemento.getDef();}public void stateDisplay() {System.out.println(角色生命力 vit);System.out.println(角色攻击力 atk);System.out.println(角色防御力 def);}public int getVit() {return vit;}public void setVit(int vit) {this.vit vit;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk atk;}public int getDef() {return def;}public void setDef(int def) {this.def def;}
}//游戏状态存储类(备忘录类)
public class RoleStateMemento {private int vit;private int atk;private int def;public RoleStateMemento(int vit, int atk, int def) {this.vit vit;this.atk atk;this.def def;}public int getVit() {return vit;}public void setVit(int vit) {this.vit vit;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk atk;}public int getDef() {return def;}public void setDef(int def) {this.def def;}
}//角色状态管理者类
public class RoleStateCaretaker {private RoleStateMemento roleStateMemento;public RoleStateMemento getRoleStateMemento() {return roleStateMemento;}public void setRoleStateMemento(RoleStateMemento roleStateMemento) {this.roleStateMemento roleStateMemento;}
}//测试类
public class Client {public static void main(String[] args) {System.out.println(------------大战Boss前------------);//大战Boss前GameRole gameRole new GameRole();gameRole.initState();gameRole.stateDisplay();//保存进度RoleStateCaretaker roleStateCaretaker new RoleStateCaretaker();roleStateCaretaker.setRoleStateMemento(gameRole.saveState());System.out.println(------------大战Boss后------------);//大战Boss时损耗严重gameRole.fight();gameRole.stateDisplay();System.out.println(------------恢复之前状态------------);//恢复之前状态gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());gameRole.stateDisplay();}
} 分析白箱备忘录模式是破坏封装性的。但是通过程序员自律同样可以在一定程度上实现模式的大部分用意。 “黑箱”备忘录模式 备忘录角色对发起人对象提供一个宽接口而为其他对象提供一个窄接口。在Java语言中实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。 将 RoleStateMemento 设为 GameRole 的内部类从而将 RoleStateMemento 对象封装在 GameRole 里面在外面提供一个标识接口 Memento 给 RoleStateCaretaker 及其他对象使用。这样 GameRole 类看到的是 RoleStateMemento 所有的接口而RoleStateCaretaker 及其他对象看到的仅仅是标识接口 Memento 所暴露出来的接口从而维护了封装型。
窄接口Memento这是一个标识接口因此没有定义出任何的方法
public interface Memento {
} 定义发起人类 GameRole并在内部定义备忘录内部类 RoleStateMemento该内部类设置为私有的
/游戏角色类
public class GameRole {private int vit; //生命力private int atk; //攻击力private int def; //防御力//初始化状态public void initState() {this.vit 100;this.atk 100;this.def 100;}//战斗public void fight() {this.vit 0;this.atk 0;this.def 0;}//保存角色状态public Memento saveState() {return new RoleStateMemento(vit, atk, def);}//回复角色状态public void recoverState(Memento memento) {RoleStateMemento roleStateMemento (RoleStateMemento) memento;this.vit roleStateMemento.getVit();this.atk roleStateMemento.getAtk();this.def roleStateMemento.getDef();}public void stateDisplay() {System.out.println(角色生命力 vit);System.out.println(角色攻击力 atk);System.out.println(角色防御力 def);}public int getVit() {return vit;}public void setVit(int vit) {this.vit vit;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk atk;}public int getDef() {return def;}public void setDef(int def) {this.def def;}private class RoleStateMemento implements Memento {private int vit;private int atk;private int def;public RoleStateMemento(int vit, int atk, int def) {this.vit vit;this.atk atk;this.def def;}public int getVit() {return vit;}public void setVit(int vit) {this.vit vit;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk atk;}public int getDef() {return def;}public void setDef(int def) {this.def def;}}
} 负责人角色类 RoleStateCaretaker 能够得到的备忘录对象是以 Memento 为接口的由于这个接口仅仅是一个标识接口因此负责人角色不可能改变这个备忘录对象的内容
//角色状态管理者类
public class RoleStateCaretaker {private Memento memento;public Memento getMemento() {return memento;}public void setMemento(Memento memento) {this.memento memento;}
}
public class Client {public static void main(String[] args) {System.out.println(------------大战Boss前------------);//大战Boss前GameRole gameRole new GameRole();gameRole.initState();gameRole.stateDisplay();//保存进度RoleStateCaretaker roleStateCaretaker new RoleStateCaretaker();roleStateCaretaker.setMemento(gameRole.saveState());System.out.println(------------大战Boss后------------);//大战Boss时损耗严重gameRole.fight();gameRole.stateDisplay();System.out.println(------------恢复之前状态------------);//恢复之前状态gameRole.recoverState(roleStateCaretaker.getMemento());gameRole.stateDisplay();}
}优点 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。 实现了内部状态的封装。除了创建它的发起人之外其他对象都不能够访问这些状态信息。 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份所有状态信息都保存在备忘录中并由管理者进行管理这符合单一职责原则。
缺点 资源消耗大。如果要保存的内部状态信息过多或者特别频繁将会占用比较大的内存资源。
使用场景 需要保存与恢复数据的场景如玩游戏时的中间结果的存档功能。 需要提供一个可回滚操作的场景如 Word、记事本、Photoshopidea等软件在编辑时按 CtrlZ 组合键还有数据库中事务操作。
3.11解释器模式 解释器模式它用于解决特定问题领域中的语言解释和表达式求值问题。它将一个语言或表达式表示为一个抽象语法树并定义了一组规则来解释和执行这个语言或表达式。 通俗地说就像我们平时使用计算器一样我们输入一个数学表达式然后计算器会解析并计算出结果。这里的计算器就是一个解释器它能够理解并执行我们输入的表达式。
解释器模式包含以下几个角色 抽象表达式Abstract Expression定义了一个抽象的解释操作所有具体表达式都要实现这个接口。 终结符表达式Terminal Expression表示语言中的终结符也就是不再进行解释的最小单位。 非终结符表达式Nonterminal Expression表示语言中的非终结符可以由终结符和其他非终结符组成复杂的表达式。 环境类Context保存解释器需要的上下文信息并提供解释器执行的方法。
下面举个例子来说明解释器模式的应用场景和工作原理 假设我们有一个简单的语言其中有两个操作符加法和乘法。我们希望能够解析并计算这个语言中的表达式。
首先我们定义抽象表达式接口
interface Expression {int interpret(Context context);
}
然后我们实现具体的终结符表达式和非终结符表达式
class NumberExpression implements Expression {private int number;public NumberExpression(int number) {this.number number;}public int interpret(Context context) {return number;}
}class AddExpression implements Expression {private Expression left;private Expression right;public AddExpression(Expression left, Expression right) {this.left left;this.right right;}public int interpret(Context context) {return left.interpret(context) right.interpret(context);}
}class MultiplyExpression implements Expression {private Expression left;private Expression right;public MultiplyExpression(Expression left, Expression right) {this.left left;this.right right;}public int interpret(Context context) {return left.interpret(context) * right.interpret(context);}
}
最后我们定义一个环境类来保存上下文信息并提供解释器执行的方法
class Context {private MapString, Integer variables new HashMap();public void setVariable(String name, int value) {variables.put(name, value);}public int getVariable(String name) {return variables.get(name);}
}
现在我们可以使用解释器模式来解析和计算表达式了
public class Client {public static void main(String[] args) {// 创建上下文对象Context context new Context();// 设置变量的值context.setVariable(a, 5);context.setVariable(b, 3);// 创建表达式Expression expression new AddExpression(new NumberExpression(context.getVariable(a)),new MultiplyExpression(new NumberExpression(2),new NumberExpression(context.getVariable(b))));// 解释并计算表达式int result expression.interpret(context);System.out.println(Result: result); // 输出Result: 11}
} 这个例子中我们定义了一个简单的语言可以解析和计算加法和乘法表达式。通过使用解释器模式我们可以灵活地扩展和修改语言的语法规则。
优点 易于改变和扩展文法。 由于在解释器模式中使用类来表示语言的文法规则因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类因此可以方便地实现一个简单的语言。 实现文法较为容易。 在抽象语法树中每一个表达式节点类的实现方式都是相似的这些类的代码编写都不会特别复杂。 增加新的解释表达式较为方便。 如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类原有表达式类代码无须修改符合 开闭原则。
缺点 对于复杂文法难以维护。 在解释器模式中每一条规则至少需要定义一个类因此如果一个语言包含太多文法规则类的个数将会急剧增加导致系统难以管理和维护。 执行效率较低。 由于在解释器模式中使用了大量的循环和递归调用因此在解释较为复杂的句子时其速度很慢而且代码的调试过程也比较麻烦。
使用场景 当语言的文法较为简单且执行效率不是关键问题时。 当问题重复出现且可以用一种简单的语言来进行表达时。 当一个语言需要解释执行并且语言中的句子可以表示为一个抽象语法树的时候。
撒花完结有不足的地方希望大家海涵有需要其他资料的宝子们加QQ群526519295