Java设计模式
Java设计模式
简介
特别说明:部分模式解释来自菜鸟教程
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
1、什么是 GOF(四人帮,全拼 Gang of Four)?
在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。
四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。
- 对接口编程而不是对实现编程。
- 优先使用对象组合而不是继承。
2、设计模式的使用
设计模式在软件开发中的两个主要用途。
3、开发人员的共同平台
设计模式提供了一个标准的术语系统,且具体到特定的情景。例如,单例设计模式意味着使用单个对象,这样所有熟悉单例设计模式的开发人员都能使用单个对象,并且可以通过这种方式告诉对方,程序使用的是单例模式。
4、最佳的实践
设计模式已经经历了很长一段时间的发展,它们提供了软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员通过一种简单快捷的方式来学习软件设计。
5、设计模式的类型
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。当然,我们还会讨论另一类设计模式:J2EE 设计模式。
序号 | 模式 & 描述 | 包括 |
---|---|---|
1 | 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 | 工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern) |
2 | 结构型模式 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 | 适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) |
3 | 行为型模式 这些设计模式特别关注对象之间的通信。 | 责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern) |
4 | J2EE 模式 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。 | MVC 模式(MVC Pattern) 业务代表模式(Business Delegate Pattern) 组合实体模式(Composite Entity Pattern) 数据访问对象模式(Data Access Object Pattern) 前端控制器模式(Front Controller Pattern) 拦截过滤器模式(Intercepting Filter Pattern) 服务定位器模式(Service Locator Pattern) 传输对象模式(Transfer Object Pattern) |
6、设计模式的七大原则
1、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
7、单一职责原则
控制类的粒度大小、将对象解耦、提高其内聚性。
创建型模式
一、工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
1、介绍
**意图:**定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
**主要解决:**主要解决接口选择的问题。
**何时使用:**我们明确地计划不同条件下创建不同实例时。
**如何解决:**让其子类实现工厂接口,返回的也是一个抽象的产品。
**关键代码:**创建过程在其子类执行。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
**缺点:**每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
**注意事项:**作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
2、核心本质
- 实例化对象不用new,用工厂方法代替
- 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
3、两种模式
①、简单工厂模式
- 简单工厂模式(静态工厂模式)
- 用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
/**
* 汽车接口
*/
public interface Car {
void name();
}
/**
* 五菱汽车类
*/
public class Wuling implements Car{
@Override
public void name() {
System.out.println("五菱宏光");
}
}
/**
* 特斯拉汽车类
*/
public class Tesla implements Car{
@Override
public void name() {
System.out.println("特斯拉");
}
}
/**
* 汽车工厂类
*/
//静态工厂模式
//增加一个的新的产品,如果不修改代码,无法做到!
//没有遵循开闭原则
public class CarFactory {
//造车方法createCar(String car)
public static Car createCar(String car){
if(car.equals("五菱宏光")){
return new Wuling();
}else if (car.equals("特斯拉")){
return new Tesla();
}else
{
return null;
}
}
}
/**
* 客户消费类
*/
public class Consumer {
public static void main(String[] args) {
//缺点:实例化对象要new
// Wuling car1 = new Wuling();
// Tesla car2 = new Tesla();
//方法1:简单工厂模式
Car car1 = CarFactory.createCar("五菱宏光");
Car car2 = CarFactory.createCar("特斯拉");
car1.name();
car2.name();
}
}
简单工厂模式虽然不满足开闭原则,但是实际中用得较多。
②、工厂方法模式
- 工厂方法模式
- 用来生产同一等级结构中的固定产品(支持增加任意产品)
/**
* 汽车接口
*/
public interface Car {
void name();
}
/**
* 五菱汽车类
*/
public class Wuling implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
/**
* 特斯拉汽车类
*/
public class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
/**
* 工厂方法模式
*/
//车工厂
public interface CarFactory {
Car getCar();
}
/**
* 五菱宏光车工厂
*/
public class WulingFactory implements CarFactory{
@Override
public Car getCar() {
return new Wuling();
}
}
/**
* 特斯拉车工厂
*/
public class TeslaFactory implements CarFactory{
@Override
public Car getCar() {
return new Tesla();
}
}
/**
* 客户消费类
*/
public class Consumer {
public static void main(String[] args) {
Car car1 = new WulingFactory().getCar();
Car car2 = new TeslaFactory().getCar();
car1.name();
car2.name();
}
}
工厂方法模式虽然满足开闭原则,但是很明显如果需要新增一个产品如大众,就需要扩展一套大众的代码
/**
* 大众汽车类
*/
public class Dazhon implements Car{
@Override
public void name() {
System.out.println("大众");
}
}
/**
* 大众汽车工厂类
*/
public class DazhonFactory implements CarFactory{
@Override
public Car getCar() {
return new Dazhon();
}
}
/**
* 客户消费类
*/
public class Consumer {
public static void main(String[] args) {
Car car1 = new WulingFactory().getCar();
Car car2 = new TeslaFactory().getCar();
Car car3 = new DazhonFactory().getCar();
car1.name();
car2.name();
car3.name();
}
}
总结:简单工厂模式和工厂方法模式比较
结构复杂度:简单工厂模式完胜
代码复杂度:简单工厂模式完胜
编程复杂度:简单工厂模式完胜
管理上的复杂度:简单工厂模式完胜
根据设计原则:工厂方法模式!
根据实际业务:简单工厂模式!
二、抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
1、介绍
**意图:**提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
**主要解决:**主要解决接口选择的问题。
**何时使用:**系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
**如何解决:**在一个产品族里面,定义多个产品。
**关键代码:**在一个工厂里聚合多个同类产品。
**优点:**当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
**缺点:**产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
使用场景: 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。
**注意事项:**产品族难扩展,产品等级易扩展。
- 抽象工厂模式
- 围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂。
/**
* 手机产品接口
*/
public interface PhoneProduct {
//开机
void start();
//关机
void shutdown();
//打电话
void call();
//发消息
void sendSms();
}
/**
* 路由器产品接口
*/
public interface RouterProduct {
//开机
void start();
//关机
void shutdown();
//开wifi
void openWifi();
//设置参数
void setIng();
}
小米产商的产品族:小米手机和小米路由器
/**
* 小米手机产品
*/
public class XiaomiPhone implements PhoneProduct{
@Override
public void start() {
System.out.println("小米手机开机");
}
@Override
public void shutdown() {
System.out.println("小米手机关机");
}
@Override
public void call() {
System.out.println("小米手机打电话");
}
@Override
public void sendSms() {
System.out.println("小米手机发消息");
}
}
/**
* 小米路由器产品
*/
public class XiaomiRouter implements RouterProduct{
@Override
public void start() {
System.out.println("小米路由器开机");
}
@Override
public void shutdown() {
System.out.println("小米路由器关机");
}
@Override
public void openWifi() {
System.out.println("打开小米路由器");
}
@Override
public void setIng() {
System.out.println("设置小米路由器参数");
}
}
华为产商的产品族:华为手机和华为路由器
/**
* 华为手机产品
*/
public class HuaWeiPhone implements PhoneProduct{
@Override
public void start() {
System.out.println("华为手机开机");
}
@Override
public void shutdown() {
System.out.println("华为手机关机");
}
@Override
public void call() {
System.out.println("华为手机打电话");
}
@Override
public void sendSms() {
System.out.println("华为手机发消息");
}
}
/**
* 华为路由器产品
*/
public class HuaWeiRouter implements RouterProduct{
@Override
public void start() {
System.out.println("华为路由器开机");
}
@Override
public void shutdown() {
System.out.println("华为路由器关机");
}
@Override
public void openWifi() {
System.out.println("打开华为路由器");
}
@Override
public void setIng() {
System.out.println("设置华为路由器参数");
}
}
定义一个抽象工厂类:定义生产手机产品和生产路由器产品两个接口,让各大产商的工厂类分别去实现其接口。
/**
* 抽象产品工厂类
*/
public interface ProductFactory {
//生产手机产品
PhoneProduct createPhoneProduct();
//生产路由器产品
RouterProduct createRouterProduct();
}
小米工厂类实现抽象工厂类去生产小米手机产品和小米路由器产品
/**
* 小米手机工厂类
*/
public class XiaomiFactory implements ProductFactory{
@Override
public PhoneProduct createPhoneProduct() {
return new XiaomiPhone();
}
@Override
public RouterProduct createRouterProduct() {
return new XiaomiRouter();
}
}
华为工厂类实现抽象工厂类去生产华为手机产品和华为路由器产品
/**
* 华为手机工厂类
*/
public class HuaWeiFactory implements ProductFactory{
@Override
public PhoneProduct createPhoneProduct() {
return new HuaWeiPhone();
}
@Override
public RouterProduct createRouterProduct() {
return new HuaWeiRouter();
}
}
最后供客户端消费者去使用
/**
* 客户消费者类
*/
public class Client {
public static void main(String[] args) {
System.out.println("===========小米手机产品=============");
PhoneProduct xiaoPhone = new XiaomiFactory().createPhoneProduct();
xiaoPhone.start();
xiaoPhone.shutdown();
xiaoPhone.call();
xiaoPhone.sendSms();
System.out.println("===========小米路由器产品=============");
RouterProduct xiaoRouter = new XiaomiFactory().createRouterProduct();
xiaoRouter.start();
xiaoRouter.shutdown();
xiaoRouter.openWifi();
xiaoRouter.setIng();
System.out.println("===========华为手机产品=============");
PhoneProduct huaWeiPhone = new HuaWeiFactory().createPhoneProduct();
huaWeiPhone.start();
huaWeiPhone.shutdown();
huaWeiPhone.call();
huaWeiPhone.sendSms();
System.out.println("===========华为路由器产品=============");
RouterProduct huaWeiRouter = new HuaWeiFactory().createRouterProduct();
huaWeiRouter.start();
huaWeiRouter.shutdown();
huaWeiRouter.openWifi();
huaWeiRouter.setIng();
}
}
三、单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
1、介绍
**意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。
**主要解决:**一个全局使用的类频繁地创建与销毁。
**何时使用:**当您想控制实例数目,节省系统资源的时候。
**如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
**关键代码:**构造函数是私有的。
应用实例:
- 1、一个班级只有一个班主任。
- 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
**缺点:**没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
**注意事项:**getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
2、单例模式的六种实现方式
①、懒汉式,线程不安全
**是否 Lazy 初始化:**是
**是否多线程安全:**否
**实现难度:**易
**描述:**这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
/**
* 懒汉式,线程不安全
*/
public class lazy1 {
//私有化构造器
private lazy1(){}
private static lazy1 instance;
public static lazy1 getInstance(){
if(instance == null){
instance = new lazy1();
}
return instance;
}
//测试方法
public void test(){
System.out.println("懒汉式,线程不安全");
}
//测试
public static void main(String[] args) {
lazy1 instance1 = lazy1.getInstance();
instance1.test();
}
}
②、懒汉式,线程安全
**是否 Lazy 初始化:**是
**是否多线程安全:**是
**实现难度:**易
**描述:**这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。 优点:第一次调用才初始化,避免内存浪费。 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。 getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
同步方法加锁实现
/**
* 懒汉式,线程安全
*/
public class lazy2 {
//私有化构造器
private lazy2(){};
private static lazy2 instance;
//同步方法加锁
public static synchronized lazy2 getInstance(){
if(instance == null){
instance = new lazy2();
}
return instance;
}
//测试方法
public void test(){
System.out.println("懒汉式,线程安全");
}
//测试
public static void main(String[] args) {
lazy2 instance1 = lazy2.getInstance();
instance1.test();
}
}
③、饿汉式
**是否 Lazy 初始化:**否
**是否多线程安全:**是
**实现难度:**易
**描述:**这种方式比较常用,但容易产生垃圾对象。 优点:没有加锁,执行效率会提高。 缺点:类加载时就初始化,浪费内存。 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
/**
* 饿汉式(本身就是线程安全的)
*/
public class HungryMan {
//私有化构造器
private HungryMan(){};
//因为是饿汉式,所以一上来就new了实例对象
private static HungryMan instance = new HungryMan();
public static HungryMan getInstance(){
return instance;
}
//测试方法
public void test(){
System.out.println("饿汉式(本身就是线程安全的)");
}
//测试
public static void main(String[] args) {
HungryMan instance1 = HungryMan.getInstance();
instance1.test();
}
}
④、双检锁/双重校验锁
双检锁/双重校验锁(DCL,即 double-checked locking)
**JDK 版本:**JDK1.5 起
**是否 Lazy 初始化:**是
**是否多线程安全:**是
**实现难度:**较复杂
**描述:**这种方式采用双锁机制,安全且在多线程情况下能保持高性能。 getInstance() 的性能对应用程序很关键。
/**
* 懒汉式双检锁/双重校验锁
*/
public class DCLSingle {
//私有化构造器
private DCLSingle(){};
//第一次加锁:使用volatile关键字
private static volatile DCLSingle instance;
public static DCLSingle getInstance(){
if(instance == null){
//第二次加锁:同步代码块加锁
synchronized (DCLSingle.class){
if(instance == null){
instance = new DCLSingle();
}
}
}
return instance;
}
//测试方法
public void test(){
System.out.println("双检锁/双重校验锁");
}
//测试
public static void main(String[] args) {
DCLSingle instance1 = DCLSingle.getInstance();
instance1.test();
}
}
⑤、登记式/静态内部类
**是否 Lazy 初始化:**是
**是否多线程安全:**是
**实现难度:**一般
**描述:**这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。 这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。
/**
* 登记式/静态内部类(饿汉式)
*/
public class InnerSingle {
//在内部类里面实例化对象
private static class SingleHandler{
private static final InnerSingle INSTANCE = new InnerSingle();
}
//私有化构造器
private InnerSingle(){}
public static InnerSingle getInstance(){
return SingleHandler.INSTANCE;
}
//测试方法
public void test(){
System.out.println("登记式/静态内部类(饿汉式)");
}
//测试
public static void main(String[] args) {
InnerSingle instance = InnerSingle.getInstance();
instance.test();
}
}
⑥、枚举
**JDK 版本:**JDK1.5 起
**是否 Lazy 初始化:**否
**是否多线程安全:**是
**实现难度:**易
**描述:**这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。 不能通过 reflection attack 来调用私有构造方法。
/**
* 枚举
*/
public enum EnumSingle {
INSTANCE;
//测试方法
public void test(){
System.out.println("枚举");
}
//测试
public static void main(String[] args) {
EnumSingle instance = EnumSingle.INSTANCE;
instance.test();
}
}
⑦、经验之谈
一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。
四、建造者模式
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
1、介绍
**意图:**将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
**主要解决:**主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
**何时使用:**一些基本部件不会变,而其组合经常变化的时候。
**如何解决:**将变与不变分离开。
**关键代码:**建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
**注意事项:**与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
2、有指挥者模式
/**
* 抽象的建造者
*/
public abstract class Builder {
//一下是四个建造步骤
abstract void buildA(); //地基
abstract void buildB(); //钢筋工程
abstract void buildC(); //铺电线
abstract void buildD(); //粉刷
//得到具体的抽象的成品:房子
abstract Product getProduct();
}
/**
* 具体的建造者:工人
*/
public class Worker extends Builder{
private Product product;
//注意:工人创建产品
public Worker(){
product = new Product();
}
@Override
void buildA() {
product.setBuildA("地基");
System.out.println("地基");
}
@Override
void buildB() {
product.setBuildA("钢筋工程");
System.out.println("钢筋工程");
}
@Override
void buildC() {
product.setBuildC("铺电线");
System.out.println("铺电线");
}
@Override
void buildD() {
product.setBuildD("粉刷");
System.out.println("粉刷");
}
@Override
Product getProduct() {
return product;
}
/**
*具体的产品:房子
*/
public class Product {
private String buildA;
private String buildB;
private String buildC;
private String buildD;
public String getBuildA() {
return buildA;
}
public void setBuildA(String buildA) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
}
/**
* 指挥者:核心,负责指挥如何构建一个工程?工程如何构建 由它决定
*/
public class Director {
//指挥工人按照顺序建房子
public Product buildProduct(Builder builder){
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();
}
}
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
Director director = new Director();
Product product = director.buildProduct(new Worker());
System.out.println(product.toString());
}
}
3、无指挥者模式
/**
* 抽象建造类
*/
public abstract class Builder {
abstract Builder buildA(String msg); //可乐
abstract Builder buildB(String msg); //汉堡
abstract Builder buildC(String msg); //薯条
abstract Builder buildD(String msg); //甜点
//得到抽象的成品:套餐
abstract Product getProduct();
}
/**
*具体的建造者,因为无指挥者,所以客户就充当着指挥者的作用
*/
public class Client extends Builder{
private Product product;
public Client(){
product = new Product();
}
@Override
Builder buildA(String msg) {
product.setBuildA(msg);
return this;
}
@Override
Builder buildB(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Builder buildC(String msg) {
product.setBuildC(msg);
return this;
}
@Override
Builder buildD(String msg) {
product.setBuildD(msg);
return this;
}
@Override
Product getProduct() {
return product;
}
}
/**
* 具体的产品:套餐
*/
public class Product {
private String buildA = "可乐";
private String buildB = "汉堡";
private String buildC = "薯条";
private String buildD = "甜点";
public String getBuildA() {
return buildA;
}
public void setBuildA(String buildA) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
}
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
Client client = new Client();
// Product product = client.getProduct();
Product product = client.buildA("全家桶").buildB("鸡翅").buildC("鸭脖").buildD("鸡腿").getProduct();
System.out.println(product.toString());
}
}
五、原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
1、介绍
**意图:**用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
**主要解决:**在运行期建立和删除原型。
何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
**如何解决:**利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码: 1、实现克隆操作,在 JAVA 实现 Cloneable 接口,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
**注意事项:**与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
原来我们创建对象时是new出来的,使用原型模式就是通过clone方法克隆一份对象
2、两种克隆
①、浅克隆
当我们修改了Date的值,V1的Date值和V2的Date值不一样 这样就实现了V2Date的值是指向了V1Date值的。
/**
* 视频类
* 1、实现一个接口:cloneable
* 2、重写clone方法
*/
public class Video implements Cloneable{
private String name;
private Date date;
public Video() {
}
public Video(String name, Date date) {
this.name = name;
this.date = date;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", date=" + date +
'}';
}
}
/**
* 客户端克隆
*/
public class Clone {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象
Date date = new Date();
Video v1 = new Video("sixkey learning java", date);
//浅克隆
//以前如果我们需要其他的Video对象v2
//Video v2 = new Video("sixkey learning java", date);
//现在,我们通过clone方式克隆出一个新对象
Video v2 = (Video) v1.clone();//克隆出来的对象和原来是一模一样的
//输出结果是一样的
System.out.println(v1 + " " + "v1.hashCode->" + v1.hashCode());
System.out.println(v2 + " " + "v2.hashCode->" + v2.hashCode());
System.out.println("============================");
//但是当我们修改了Date的值,V1的Date值和V2的Date值还是一样
//说明V2的Date值是指向了V1的Date值的。
date.setDate(12345645);
System.out.println(v1 + " " + "v1.hashCode->" + v1.hashCode());
System.out.println(v2 + " " + "v2.hashCode->" + v2.hashCode());
}
}
②、深克隆
当我们修改了Date的值,V1的Date值和V2的Date值不一样 这样就实现了V2Date的值是指向了自己的。
/**
* 视频类
* 1、实现一个接口:cloneable
* 2、重写clone方法
*/
public class Video implements Cloneable{
private String name;
private Date date;
public Video() {
}
public Video(String name, Date date) {
this.name = name;
this.date = date;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object clone = super.clone();
//实现深克隆
Video v = (Video) clone;
//将这个对象的属性也克隆
v.date = (Date) this.date.clone();
return clone;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", date=" + date +
'}';
}
}
/**
* 客户端克隆
*/
public class Clone {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象
Date date = new Date();
Video v1 = new Video("sixkey learning java", date);
//深克隆
//以前如果我们需要其他的Video对象v2
//Video v2 = new Video("sixkey learning java", date);
//现在,我们通过clone方式克隆出一个新对象
Video v2 = (Video) v1.clone();//克隆出来的对象和原来是一模一样的
//输出结果是一样的
System.out.println(v1 + " " + "v1.hashCode->" + v1.hashCode());
System.out.println(v2 + " " + "v2.hashCode->" + v2.hashCode());
System.out.println("============================");
//但是当我们修改了Date的值,V1Date值和V2Date值不一样
//说明V2Date值是指向了自己的Date值。
date.setDate(12345645);
System.out.println(v1 + " " + "v1.hashCode->" + v1.hashCode());
System.out.println(v2 + " " + "v2.hashCode->" + v2.hashCode());
}
}
结构性模式
一、适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
我们通过下面的实例来演示适配器模式的使用。其中,音频播放器设备只能播放 mp3 文件,通过使用一个更高级的音频播放器来播放 vlc 和 mp4 文件。
1、介绍
**意图:**将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
**主要解决:**主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
**如何解决:**继承或依赖(推荐)。
**关键代码:**适配器继承或依赖已有的对象,实现想要的目标接口。
应用实例: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
**使用场景:**有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
**注意事项:**适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
2、两种适配器
①、类适配器
/**
* 要求被适配的实体类:网线
*/
public class Adaptee {
public void request(){
System.out.println("网线可以上网!");
}
}
/**
* 适配器接口(转换器):用来处理适配请求
*/
public interface NetToUsb {
void handleRequest();
}
/**
* 继承方式(单继承,有局限性)
* 具体的适配器类:这是类适配器的写法(转换器的实现)
*/
public class Adapter extends Adaptee implements NetToUsb {
@Override
public void handleRequest() {
super.request();
}
}
/**
* 客户端类:想上网,但是网线接口接不上,需要一个适配器
*/
public class Computer {
public void network(NetToUsb adapter){
//上网的方法:具体实现
adapter.handleRequest();
}
public static void main(String[] args) {
Computer computer = new Computer(); //电脑
NetToUsb netToUsb = new Adapter(); //适配器(转换器)
Adaptee adaptee = new Adaptee(); //网线
computer.network(netToUsb);
}
}
②、对象适配器
/**
* 要求被适配的类:网线
*/
public class Adaptee {
public void request(){
System.out.println("网线可以上网!");
}
}
/**
* 适配器接口:用来处理适配请求
*/
public interface NetToUsb {
void handleRequest();
}
/**
* 对象适配器:组合方式
* 具体的适配器类:这是类适配器的写法
*/
public class Adapter implements NetToUsb {
//将要求被适配的类:网线组合进来
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void handleRequest() {
adaptee.request();
}
}
/**
* 客户端类:想上网,但是网线接口接不上,需要一个适配器
*/
public class Computer {
public void network(NetToUsb adapter){
//上网的方法:具体实现
adapter.handleRequest();
}
public static void main(String[] args) {
Computer computer = new Computer(); //电脑
Adaptee adaptee = new Adaptee(); //网线
Adapter adapter = new Adapter(adaptee); //适配器(转换器)
computer.network(adapter);
}
}
二、桥接模式
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
我们通过下面的实例来演示桥接模式(Bridge Pattern)的用法。其中,可以使用相同的抽象类方法但是不同的桥接实现类,来画出不同颜色的圆。
1、介绍
**意图:**将抽象部分与实现部分分离,使它们都可以独立的变化。
**主要解决:**在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
**何时使用:**实现系统可能有多个角度分类,每一种角度都可能变化。
**如何解决:**把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
**关键代码:**抽象类依赖实现类。
应用实例: 1、猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。 2、墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。
优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
**缺点:**桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
使用场景: 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。 2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
**注意事项:**对于两个独立变化的维度,使用桥接模式再适合不过了。

/**
* 品牌接口
*/
public interface Brand {
void info();
}
/**
* 联想品牌
*/
public class Lenovo implements Brand{
@Override
public void info() {
System.out.print("联想");
}
}
/**
* 苹果品牌
*/
public class Apple implements Brand{
@Override
public void info() {
System.out.print("苹果");
}
}
/**
* 电脑抽象类
*/
public abstract class Computer {
//组合品牌---桥接的最好体现。
protected Brand brand;
public Computer(Brand brand){
this.brand = brand;
}
public void info(){
//电脑自带品牌
brand.info();
}
}
//桌面电脑
class desktop extends Computer{
public desktop(Brand brand) {
super(brand);
}
@Override
public void info() {
super.info();
System.out.println("台式机");
}
}
//笔记本
class laptop extends Computer{
public laptop(Brand brand) {
super(brand);
}
@Override
public void info() {
super.info();
System.out.println("笔记本");
}
}
/**
* 测试
*/
public class Test {
public static void main(String[] args) {
//联想品牌的笔记本电脑
Computer computer1 = new laptop(new Lenovo());
computer1.info();
//苹果品牌的台式机电脑
Computer computer2 = new desktop(new Apple());
computer2.info();
//输出结果:
//联想笔记本
//苹果台式机
}
}
三、代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
介绍
**意图:**为其他对象提供一种代理以控制对这个对象的访问。
**主要解决:**在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
**何时使用:**想在访问一个类时做一些控制。
**如何解决:**增加中间层。
**关键代码:**实现与被代理类组合。
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、一个真实角色就会产生一个代理角色;代码量就会翻倍,开发效率会变低。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
**使用场景:**按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
静态代理
1、角色分析
- 抽象角色(租房):一般会使用接口或者抽象类来解决。
- 真实角色:被代理的角色(房东)。
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作。
- 真实角色(客户):访问代理对象的人。
抽象角色(接口):说白了就是需要做的业务
/**
* 租房接口
*/
public interface Rent {
//出租房子方法
void rent();
}
真实角色:被代理的角色(房东)。
/**
* 房东
*/
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
代理角色(中介)
/**
* 代理角色(中介)
*/
public class Proxy implements Rent{
//组合房东
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host = host;
}
@Override
public void rent() {
//代理房东出租房子
host.rent();
//额外干其他事情
seeHouse();
heTong();
}
//中介可以额外干其他事情
public void seeHouse(){
System.out.println("中介带你看房子");
}
public void heTong(){
System.out.println("签租赁合同");
}
}
真实角色(客户)
/**
* 客户、租房子的人
*/
public class Client {
public static void main(String[] args) {
//方式1、直接找房东租房子
Host host = new Host();
//host.rent();
//方式2、通过中介租房子,但是呢,代理角色一般会有其他附属操作。
Proxy proxy = new Proxy(host);
//你不用面对房东,直接找中介租房即可。
proxy.rent();
//输出结果:
//房东出租房子
//中介带你看房子
//签租赁合同
}
}
2、加深理解
业务需求
业务需求,在原有的增删改查功能代码之上实现日志功能,不改动原有代码。 代码直接加在代理角色类中,这样就不用改动原有代码了。
抽象角色(接口)
/**
* 抽象角色(接口)
*/
public interface UserService {
void add();
void delete();
void update();
void query();
}
/**
* 真实角色
*/
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("add方法");
}
@Override
public void delete() {
System.out.println("delete方法");
}
@Override
public void update() {
System.out.println("update方法");
}
@Override
public void query() {
System.out.println("query方法");
}
}
真实角色
/**
* 真实角色
*/
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("add方法");
}
@Override
public void delete() {
System.out.println("delete方法");
}
@Override
public void update() {
System.out.println("update方法");
}
@Override
public void query() {
System.out.println("query方法");
}
}
代理角色
/**
* 代理角色
* 业务需求,在原有的增删改查功能代码之上加入日志,不改动原有代码。
* 代码直接加在代理角色类中,这样就不用改动原有代码了。
*/
public class UserServiceProxy implements UserService{
//组合抽象角色
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
public void log(String meg){
System.out.println("使用了" + meg + "方法");
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
//使用代理模式
UserServiceProxy userServiceProxy = new UserServiceProxy();
userServiceProxy.setUserService(userService);
userServiceProxy.add();
}
}
动态代理
1、介绍
- 动态代理的代理角色和静态代理的代理角色一样。
- 动态代理的代理类是动态生成的,不是像静态代理一样直接把代理类Proxy写好。
- 需要了解两个类:Proxy(类)、 InvocationHandler(接口)
public interface InvocationHandler
InvocationHandler
是由代理实例的调用处理程序实现的接口 。
每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke
方法。
2、动态代理实例
实例1
抽象角色(接口):说白了就是需要做的业务
/**
* 租房接口
*/
public interface Rent {
//出租房子方法
void rent();
}
真实角色:被代理的角色(房东)。
/**
* 房东(真实角色)
*/
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
动态生成代理类
/**
* 等会儿我们会使用这个类,自动生成代理类
*/
public class ProxyInvocationHandler implements InvocationHandler {
//被代理接口
private Rent rent;
public void setRent(Rent rent){
this.rent = rent;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),//当前类加载器
rent.getClass().getInterfaces(),//被代理接口
this);//InvocationHandler对象
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {
//动态代理的实质,就是使用反射机制实现
seeHouse();
fare();
Object invoke = method.invoke(rent, args);
return invoke;
}
//代理类的额外附属工作
public void seeHouse(){
System.out.println("中介带你看房子");
}
public void fare(){
System.out.println("收中介费");
}
}
测试
/**
* 测试
*/
public class Client {
public static void main(String[] args) {
//真实角色房东
Host host = new Host();
//代理角色,现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host);
//得到代理类(中介),中介替房东执行rent().
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
//输出结果
//中介带你看房子
//收中介费
//房东出租房子
}
}
实例2
业务需求
业务需求,在原有的增删改查功能代码之上实现日志功能,不改动原有代码。 代码直接加在自动生成代理类的invoke方法中,这样就不用改动原有代码了。
抽象角色(接口)
/**
* 抽象角色(接口)
*/
public interface UserService {
void add();
void delete();
void update();
void query();
}
真实角色
/**
* 真实角色
*/
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("add方法");
}
@Override
public void delete() {
System.out.println("delete方法");
}
@Override
public void update() {
System.out.println("update方法");
}
@Override
public void query() {
System.out.println("query方法");
}
}
代理角色
/**
* 等会儿我们会使用这个类,自动生成代理类
*/
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private UserService userService;
public void setUserService(UserService userService){
this.userService = userService;
}
//自动生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
this);
}
//处理代理类,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理实质就是反射机制实现
log(method.getName());//使用反射获取执行方法的名称
Object invoke = method.invoke(userService, args);
return invoke;
}
//日志输出
public void log(String meg){
System.out.println("执行了" + meg + "方法");
}
}
测试
/**
* 测试
*/
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理类,现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setUserService(userService);
//得到代理类
UserService proxy = (UserService) pih.getProxy();
proxy.query();
//输出结果
//执行了query方法
//query方法
}
}
3、动态代理工具包
工具包的用法就是我们只需要设置被代理类的对象即可,会自动生成代理类对象。
/**
* 万能自动生成代理类工具包
*/
public class ProxyInvocationHandler implements InvocationHandler {
//被代理接口
private Object target;
public void setTarget(Object target){
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理
Object invoke = method.invoke(target, args);
return invoke;
}
}
测试
/**
* 测试
*/
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理类,现在还没有,设置被代理类的对象
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(host);
//得到代理类
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理类,现在还没有,设置被代理类的对象
pih.setTarget(userService);
//得到代理类
UserService proxy1 = (UserService) pih.getProxy();
proxy1.add();
}
}
四、装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
1、介绍
**意图:**动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
**主要解决:**一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
**何时使用:**在不想增加很多子类的情况下扩展类。
**如何解决:**将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
**优点:**装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
**缺点:**多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
**注意事项:**可代替继承。
引出问题

问题解决


解决了当我们新增一种单品咖啡时继承带来的类爆炸问题
UML图

主要分为两类:装饰者和被装饰者;装饰者就是被附加的职责。
被装饰者:具体的饮料——咖啡
/**
* 被装饰者:抽象类:饮料
*/
public abstract class Drink {
//描述
private String description;
//价格
private float price = 0.0f;
//计算费用抽象方法,有子类来实现
public abstract float cost();
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
/**
* 饮料的具体子类:单品咖啡
*/
public class Coffee extends Drink{
/**
* 因为是单品咖啡,所以直接返回父类的价格即可。
* @return
*/
@Override
public float cost() {
return super.getPrice();
}
}
/**
* 咖啡的具体子类:美式咖啡
*/
public class LongBlack extends Coffee{
public LongBlack(){
setDescription("美式咖啡--LongBlack");
setPrice(5.0f);
}
}
装饰者:具体的调料品—–牛奶、豆浆…
/**
* 装饰者 : 调料
*/
public class Decorator extends Drink{
//装饰者模式的核心就是继承了被装饰者的同时组合了被装饰者
private Drink drink;
public Decorator(Drink drink){
this.drink = drink;
}
//费用 = 具体调料 + 具体饮料
@Override
public float cost() {
//调料品 + 饮料 的费用
return super.getPrice() + drink.cost();
}
@Override
public String getDescription() {
return getDescription() + " " + getPrice() + "&&" + drink.getDescription();
}
}
/**
* 装饰者的具体子类:牛奶调料品
*/
public class Milk extends Decorator{
public Milk(Drink drink) {
super(drink);
setDescription("牛奶调料品");
setPrice(3.5f);
}
}
测试
/**
* 下单者:客户
*/
public class Client {
public static void main(String[] args) {
//装饰者模式下的订单:2份巧克力 + 1份牛奶的美式咖啡。
Drink longBlack = new LongBlack();
System.out.println("单品美式咖啡费用:" + longBlack.cost());
//加入一份牛奶
longBlack = new Milk(longBlack);
System.out.println("加入一份牛奶后的美式咖啡费用:" + longBlack.cost());
//加入1份巧克力
longBlack = new Chocolate(longBlack);
System.out.println("加入1份巧克力和一份牛奶后的美式咖啡费用:" + longBlack.cost());
}
}
//输出结果
单品美式咖啡费用:5.0
加入一份牛奶后的美式咖啡费用:8.5
加入1份巧克力和一份牛奶后的美式咖啡费用:11.5
方便之处就在:当商家需要添加一个中式咖啡品种时,只需直接继承被装饰者抽象类Drink即可与装饰者调料品有联系。
/**
* 中式咖啡
*/
public class Descf extends Coffee{
public Descf(){
setDescription("中式咖啡");
setPrice(6.0f);
}
}
测试
/**
* 下单者:客户
*/
public class Client {
public static void main(String[] args) {
//中式咖啡加豆浆再加牛奶
Drink descf = new Descf();
//加入一份豆浆
descf = new Soy(descf);
//加入一份牛奶
descf = new Milk(descf);
System.out.println("加入一份豆浆、一份牛奶的中式咖啡价格为:" + descf.cost());
}
}
//输出结果
加入一份豆浆、一份牛奶的中式咖啡价格为:12.0
2、装饰者模式JDK应用

五、组合模式
组合模式(Composite Pattern),又叫部分-整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
1、介绍
**意图:**将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
**主要解决:**它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
**如何解决:**树枝和叶子实现统一接口,树枝内部组合该接口。
**关键代码:**树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
优点: 1、高层模块调用简单。 2、节点自由增加。
**缺点:**在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
**使用场景:**部分、整体场景,如树形菜单,文件、文件夹的管理。
**注意事项:**定义时为具体类。
问题引出

问题解决


UML图

核心代码实现
/**
* 组合模式
* 组织机构可以是抽象类或者接口
*/
public abstract class OrganziationComponent {
//名称
private String name;
//描述
private String description;
public OrganziationComponent(String name, String description) {
this.name = name;
this.description = description;
}
//添加方法,叶子结点子类不用实现
protected void add(OrganziationComponent organziationComponent){
//默认实现
throw new UnsupportedOperationException();
}
//删除方法,叶子结点子类不用实现
protected void remove(OrganziationComponent organziationComponent){
//默认实现
throw new UnsupportedOperationException();
}
//打印方法,所有子类都需实现
protected abstract void print();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
/**
* University是学校,这是管理者,可以管理学院college
*/
public class University extends OrganziationComponent{
//组合模式核心:聚合OrganziationComponent
List<OrganziationComponent> organziationComponents = new ArrayList<>();
public University(String name, String description){
super(name,description);
}
//重写add方法
@Override
protected void add(OrganziationComponent organziationComponent) {
organziationComponents.add(organziationComponent);
}
//重写remove方法
@Override
protected void remove(OrganziationComponent organziationComponent) {
organziationComponents.remove(organziationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDescription() {
return super.getDescription();
}
//打印出当前学校的名称以及学校下的学院名称
@Override
protected void print() {
//打印学校名称
System.out.println("---------------" +getName() + "-----------------");
//打印学院名称
for (OrganziationComponent organziationComponent : organziationComponents) {
organziationComponent.print();
}
}
}
/**
* College是学院,这是管理者,可以管理专业major
*/
public class College extends OrganziationComponent{
//组合模式核心:聚合OrganziationComponent
List<OrganziationComponent> organziationComponents = new ArrayList<>();
public College(String name, String description){
super(name,description);
}
//重写add方法
@Override
protected void add(OrganziationComponent organziationComponent) {
//将来业务中,College 和 University的add方法不一定完全一样。
organziationComponents.add(organziationComponent);
}
//重写remove方法
@Override
protected void remove(OrganziationComponent organziationComponent) {
organziationComponents.remove(organziationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDescription() {
return super.getDescription();
}
//打印出当前学校的名称以及学校下的学院名称
@Override
protected void print() {
//打印学校名称
System.out.println("---------------" +getName() + "-----------------");
//打印学院名称
for (OrganziationComponent organziationComponent : organziationComponents) {
organziationComponent.print();
}
}
}
/**
* Major是专业,这是叶子结点,下面不再有分支。
*/
public class Major extends OrganziationComponent{
public Major(String name, String description) {
super(name, description);
}
//不再重写add和remove方法,因为它是叶子结点。
@Override
public String getName() {
return super.getName();
}
@Override
public String getDescription() {
return super.getDescription();
}
@Override
protected void print() {
System.out.println("==========" + getName() + "==========");
}
}
测试
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//从大到小创建对象 学校
OrganziationComponent university = new University("清华大学", "中国顶级大学");
//学院
OrganziationComponent computerCollege = new College("信息工程学院", "信息工程学院");
OrganziationComponent maCollege = new College("马克思主义学院", "马克思主义学院");
//专业
computerCollege.add(new Major("计算机科学与技术","很不错"));
computerCollege.add(new Major("软件工程","明年就不招咯"));
computerCollege.add(new Major("信管","可以"));
maCollege.add(new Major("马克思主义","难学"));
maCollege.add(new Major("毛概","不错"));
//将学院加入到学校中去
university.add(computerCollege);
university.add(maCollege);
//打印学校、学院、专业
university.print();
//输出结果
// ---------------清华大学-----------------
//---------------信息工程学院-----------------
//==========计算机科学与技术==========
//==========软件工程==========
//==========信管==========
//---------------马克思主义学院-----------------
//==========马克思主义==========
//==========毛概==========
computerCollege.print();
//输出结果
// ---------------信息工程学院-----------------
//==========计算机科学与技术==========
//==========软件工程==========
//==========信管==========
}
}
六、外观模式
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
1、介绍
**意图:**为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
**主要解决:**降低访问复杂系统的内部子系统时的复杂度,简化客户端之间的接口。
何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。
**如何解决:**客户端不与系统耦合,外观类与系统耦合。
**关键代码:**在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
应用实例: 1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。 2、JAVA 的三层开发模式。
优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。
**缺点:**不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。
**注意事项:**在层次化结构中,可以使用外观模式定义系统中每一层的入口。
引出问题

分析问题

外观模式原理图

UML图

核心代码:主要是定义一个高层接口,让客户端直接调用高层接口即可。
高层接口:
/**
* 外观模式
* 家庭影院:高层接口,被client直接调用
*/
public class HomeMoveFacade {
//核心:聚合各个子系统
private DVDPlayer dvdPlayer;
private Popcorn popcorn;
private Projector projector;
//初始化子系统
public HomeMoveFacade() {
this.dvdPlayer = DVDPlayer.getInstance();
this.popcorn = Popcorn.getInstance();
this.projector = Projector.getInstance();
}
//做准备工作
public void ready(){
//dvd开机
dvdPlayer.on();
//爆米花机开机
popcorn.on();
//投影仪开机
projector.on();
}
//开始进行工作
public void play(){
//dvd播放
dvdPlayer.play();
//爆米花机工作
popcorn.pop();
//投影仪开始聚焦
projector.focus();
}
//暂停
public void pause(){
dvdPlayer.pause();
popcorn.pause();
projector.pause();
}
//关机
public void end(){
//dvd播放关机
dvdPlayer.off();
//爆米花机关机
popcorn.off();
//投影仪关机
projector.off();
}
}
子系统:
/**
* DVD子系统
*/
public class DVDPlayer {
//采用饿汉式单例模式
private DVDPlayer(){};
private static DVDPlayer instance = new DVDPlayer();
public static DVDPlayer getInstance(){
return instance;
}
//开机方法
public void on(){
System.out.println("DVD 开机");
}
//关机方法
public void off(){
System.out.println("DVD 关机");
}
//播放方法
public void play(){
System.out.println("DVD 播放...");
}
//结束方法
public void pause(){
System.out.println("DVD 暂停...");
}
}
/**
* 爆米花机子系统
*/
public class Popcorn {
//采用饿汉式单例模式
private Popcorn(){};
private static Popcorn instance = new Popcorn();
public static Popcorn getInstance(){
return instance;
}
//开机方法
public void on(){
System.out.println("爆米花机 开机");
}
//关机方法
public void off(){
System.out.println("爆米花机 关机");
}
public void pop(){
System.out.println("爆米花机 工作...");
}
//赞停
public void pause(){
System.out.println("爆米花机 暂停");
}
}
/**
* 投影仪子系统
*/
public class Projector {
//采用饿汉式单例模式
private Projector(){};
private static Projector instance = new Projector();
public static Projector getInstance(){
return instance;
}
//开机方法
public void on(){
System.out.println("投影仪 开机");
}
//关机方法
public void off(){
System.out.println("投影仪 关机");
}
//聚焦方法
public void focus(){
System.out.println("投影仪 聚焦....");
}
//结束
public void pause(){
System.out.println("投影仪 聚焦暂停");
}
}
测试:
/**
* 测试
*/
public class Client {
public static void main(String[] args) {
HomeMoveFacade homeMoveFacade = new HomeMoveFacade();
//子系统开机工作
homeMoveFacade.ready();
System.out.println("--------------------------");
//子系统开始工作
homeMoveFacade.play();
System.out.println("--------------------------");
//子系统暂停工作
homeMoveFacade.pause();
System.out.println("--------------------------");
//子系统关机
homeMoveFacade.end();
//输出结果
//DVD 开机
//爆米花机 开机
//投影仪 开机
//--------------------------
//DVD 播放...
//爆米花机 工作...
//投影仪 聚焦....
//--------------------------
//DVD 暂停...
//爆米花机 暂停
//投影仪 聚焦暂停
//--------------------------
//DVD 关机
//爆米花机 关机
//投影仪 关机
}
}
2、外观模式细节
外观模式的注意事项和细节
外观模式外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
通过合理的使用外观模式,可以帮我们更好的划分访问的层次
当系统需要进行分层设计时,可以考虑使用Facade模式
在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性
不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。
七、享元模式
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。
1、介绍
**意图:**运用共享技术有效地支持大量细粒度的对象。
**主要解决:**在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。
**如何解决:**用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
**关键代码:**用 HashMap 存储这些对象。
应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的连接池。
**优点:**大大减少对象的创建,降低系统的内存,使效率提高。
**缺点:**提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。
注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。
问题引出

享元模式原理图

2、内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态
享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态
内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题
UML图

核心代码
/**
* 网站的抽象类
*/
public abstract class WebSite {
public abstract void use(User user);
}
/**
* 具体的网站
*/
public class ConcrrentWebSite extends WebSite{
//网站类型
//共享部分,这是内部状态
private String type = "";
public ConcrrentWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布类型为:" + type + "。"+ user.getName() + "在使用中...");
}
}
/**
* 享元模式
* 生成网站的工厂,充当一个池的作用,根据需求返回具体的网站
*/
public class WebSiteFactory {
//集合,充当池的作用
Map<String,ConcrrentWebSite> pools = new HashMap<>();
//根据网站的类型,返回一个网站,如果没有就创建一个网站,并放到池中,并返回。
public WebSite getWebSite(String type){
//如果没有就创建一个网站,并放到池中
if(!pools.containsKey(type)){
pools.put(type,new ConcrrentWebSite(type));
}
return (WebSite) pools.get(type);
}
//获取网站分类的总数(池中有几个)
public int WebSiteCount(){
return pools.size();
}
}
/**
* 网站使用者,这是外部状态
*/
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试
/**
* 测试
*/
public class Client {
public static void main(String[] args) {
//创建网站工厂
WebSiteFactory factory = new WebSiteFactory();
//以新闻类型发布网站
WebSite webSite = factory.getWebSite("新闻");
webSite.use(new User("tom"));
//以博客类型发布网站
WebSite webSite1 = factory.getWebSite("博客");
webSite1.use(new User("smith"));
//以博客类型发布网站
WebSite webSite2 = factory.getWebSite("博客");
webSite2.use(new User("jack"));
//以博客类型发布网站
WebSite webSite3 = factory.getWebSite("博客");
webSite3.use(new User("king"));
//以博客类型发布网站
WebSite webSite4 = factory.getWebSite("博客");
webSite4.use(new User("swx"));
//返回网站分类个数
System.out.println("网站的分类共:" + factory.WebSiteCount() + "种");
//输出结果网站的发布类型为:新闻。tom在使用中...
//网站的发布类型为:博客。smith在使用中...
//网站的发布类型为:博客。jack在使用中...
//网站的发布类型为:博客。king在使用中...
//网站的发布类型为:博客。swx在使用中...
//网站的分类共:2种
}
}
3、享元模式的注意事项和细节
在享元模式这样理解,“享”就表示共享,“元”表示对象
系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池
行为型模式
一、命令模式
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
1、介绍
**意图:**将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
**主要解决:**在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
**何时使用:**在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
**如何解决:**通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。
**关键代码:**定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口
**应用实例:**struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。
优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。
**缺点:**使用命令模式可能会导致某些系统有过多的具体命令类。
**使用场景:**认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。
**注意事项:**系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式,见命令模式的扩展。
命令模式结构示意图:
基本介绍
命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象
引出问题

命令模式原理图

解决问题

UML图

核心代码
/**
* 命令执行者:电灯
*/
public class LightReceiver {
public void on(){
System.out.println(" 电灯打开了....");
}
public void off(){
System.out.println("电灯关闭了....");
}
}
/**
* 所有的命令都在这个接口中
*/
public interface Command {
//执行操作
public void execute();
//撤销操作
public void undo();
}
/**
* 打开电灯命令
*/
public class LightOnCommand implements Command{
//聚合电灯,实际操作由电灯完成
LightReceiver lightReceiver;
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
//电灯打开操作
lightReceiver.on();
}
@Override
public void undo() {
//撤销操作
lightReceiver.off();
}
}
/**
* 关闭电灯命令
*/
public class LightOffCommand implements Command{
//聚合电灯,实际操作由电灯完成
LightReceiver lightReceiver;
public LightOffCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
//电灯关闭操作
lightReceiver.off();
}
@Override
public void undo() {
//撤销操作
lightReceiver.on();
}
}
/**
* 命令调用者
*/
public class RemoteController {
//打开和关闭按钮的数组
Command[] onCommands;
Command[] offCommands;
//执行撤销的命令
Command undoCommand;
//初始化命令
public RemoteController(){
onCommands = new Command[5];
offCommands = new Command[5];
//初始化命令,刚开始时,执行空命令
for(int i = 0; i < 5; i++){
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
//给按钮设置你需要的命令
public void setCommand(int num,Command onCommand,Command offCommand){
onCommands[num] = onCommand;
offCommands[num] = offCommand;
}
//按下开按钮
public void onButtonWasPushed(int num){
//找到你按下的开的按钮,并调用对应方法
onCommands[num].execute();
//记录日志,便于撤销
undoCommand = onCommands[num];
}
//按下关按钮
public void offButtonWasPushed(int num){
//找到你按下的关的按钮,并调用对应方法
offCommands[num].execute();
//记录日志,便于撤销
undoCommand = offCommands[num];
}
//按下撤销命令
public void undoButtonWasPushed(){
//执行撤销操作
undoCommand.undo();
}
}
测试
/**
* 测试
*/
public class Client {
public static void main(String[] args) {
//使用命令模式,完成通过遥控器,对电灯的操作
//创建电灯的对象(接收者,命令执行者)
LightReceiver lightReceiver = new LightReceiver();
//创建电灯相关的命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
//需要一个遥控器,初始化空命令
RemoteController remoteController = new RemoteController();
//通过命令对电灯进行控制
remoteController.setCommand(0,lightOnCommand,lightOffCommand);
System.out.println("--------按下灯的开按钮---------");
remoteController.onButtonWasPushed(0);
System.out.println("--------按下灯的关按钮---------");
remoteController.offButtonWasPushed(0);
System.out.println("--------按下灯的撤销按钮---------");
remoteController.undoButtonWasPushed();
//输出结果
//--------按下灯的开按钮---------
// 电灯打开了....
//--------按下灯的关按钮---------
//电灯关闭了....
//--------按下灯的撤销按钮---------
// 电灯打开了....
}
}
2、命令模式的注意事项和细节
将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
容易实现对请求的撤销和重做
命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。6)命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制
二、观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
介绍
**意图:**定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
**主要解决:**一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
**何时使用:**一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
**如何解决:**使用面向对象技术,可以将这种依赖关系弱化。
**关键代码:**在抽象类里有一个 ArrayList 存放观察者们。
应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
引出问题

观察者模式原理图


UML图

核心代码
/**
* 操作观察者
*/
public interface Subject {
//注册观察者
void registerObserver(Observer observer);
//删除观察者
void removeObserver(Observer observer);
//通知天气更新方法
void notifyObserver();
}
/**
* 类是核心
* 1. 包含最新的天气情况信息
* 2. 含有 观察者集合,使用ArrayList管理
* 3. 当数据有更新时,就主动调用ArrayList,通知观察者(接入方)看到最新信息
*
*/
public class WeatherData implements Subject{
//温度
private float temperature;
//气压
private float pressure;
//湿度
private float altitude;
//观察者数组
private List<Observer> observers;
public WeatherData(){
observers = new ArrayList<Observer>();
}
public void setData(float temperature, float pressure, float altitude){
this.temperature = temperature;
this.pressure = pressure;
this.altitude = altitude;
changeData();
}
//更新数据方法
public void changeData(){
notifyObserver();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
if(observers.contains(observer)){
observers.remove(observer);
}
}
@Override
public void notifyObserver() {
//遍历观察者,推送更新数据
for (int i = 0; i < observers.size(); i++){
observers.get(i).update(this.temperature,this.pressure,this.altitude);
}
}
}
/**
* 观察者接口,由具体观察者实现
*/
public interface Observer {
//天气更新方法
void update(float temperature,float pressure,float altitude);
}
/**
* 具体的观察者
*/
public class CurrentConditions implements Observer{
//温度
private float temperature;
//气压
private float pressure;
//湿度
private float altitude;
@Override
public void update(float temperature, float pressure, float altitude) {
this.temperature = temperature;
this.pressure = pressure;
this.altitude = altitude;
disPlay();
}
public void disPlay(){
System.out.println("今天的天气温度为:" + temperature + "********");
System.out.println("今天的气压为:" + pressure + "********");
System.out.println("今天的天气湿度为:" + altitude + "********");
}
}
测试
/**
*测试类
*/
public class Client {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditions currentConditions = new CurrentConditions();
weatherData.registerObserver(currentConditions);
weatherData.setData(23f,45f,12.4f);
//输出结果
//今天的天气温度为:23.0********
//今天的气压为:45.0********
//今天的天气湿度为:12.4********
}
}
三、职责链模式
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
1、介绍
**意图:**避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
**主要解决:**职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
**何时使用:**在处理消息的时候以过滤很多道。
**如何解决:**拦截的类都实现统一接口。
**关键代码:**Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
应用实例: 1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。
优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。
使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。
**注意事项:**在 JAVA WEB 中遇到很多应用。
引出问题

UML图

对原理类图的说明-即(职责链模式的角色及职责)Approver:抽象的处理者,定义了一个处理请求的接口,同时含义另外Handler
系主任、院长…是具体的处理者,处理它自己负责的请求,可以访问它的后继者(即下一个处理者),如果可以处理当前请求,则处理,否则就将该请求交个后继者去处理,从而形成一个职责链3)
PurchaseRequest,含义很多属性,表示一个请求
核心代码
/**
* 请求
*/
public class PurchaseRequest {
private int type = 0; //请求类型
private float price = 0.0f; //处理金额
private int id = 0; //请求id
public PurchaseRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}
public int getType() {
return type;
}
public float getPrice() {
return price;
}
public int getId() {
return id;
}
}
/**
* 请求处理类
*/
public abstract class Approver {
//下一个处理类
Approver approver;
//名称
String name;
public Approver(String name) {
this.name = name;
}
//下一个处理者
public void setApprover(Approver approver){
this.approver = approver;
}
//处理审批请求的方法,得到一个请求,处理由子类完成,此方法设计成抽象
public abstract void process(PurchaseRequest purchaseRequest);
}
/**
* 具体处理请求审批类之一:系主任
*/
public class DepartMateApprover extends Approver{
public DepartMateApprover(String name) {
super(name);
}
@Override
public void process(PurchaseRequest purchaseRequest) {
//如果自己可以处理则自己处理
if(purchaseRequest.getPrice() <= 5000){
System.out.println("请求编号为:" + purchaseRequest.getId() + "的请求被" + name + "处理了");
}else {
//不能则交给下个审批人处理
approver.process(purchaseRequest);
}
}
}
/**
* 具体处理请求审批类之一:院长
*/
public class CollegeApprover extends Approver{
public CollegeApprover(String name) {
super(name);
}
@Override
public void process(PurchaseRequest purchaseRequest) {
//如果自己可以处理则自己处理
if(purchaseRequest.getPrice() > 5000 && purchaseRequest.getPrice() <= 10000){
System.out.println("请求编号为:" + purchaseRequest.getId() + "的请求被" + name + "处理了");
}else {
//不能则交给下个审批人处理
approver.process(purchaseRequest);
}
}
}
/**
* 具体处理请求审批类之一:副校长
*/
public class ViceSchoolMasterApprover extends Approver{
public ViceSchoolMasterApprover(String name) {
super(name);
}
@Override
public void process(PurchaseRequest purchaseRequest) {
//如果自己可以处理则自己处理
if(purchaseRequest.getPrice() > 10000 && purchaseRequest.getPrice() <= 30000){
System.out.println("请求编号为:" + purchaseRequest.getId() + "的请求被" + name + "处理了");
}else {
//不能则交给下个审批人处理
approver.process(purchaseRequest);
}
}
}
/**
* 具体处理请求审批类之一:校长
*/
public class SchoolMasterApprover extends Approver{
public SchoolMasterApprover(String name) {
super(name);
}
@Override
public void process(PurchaseRequest purchaseRequest) {
//如果自己可以处理则自己处理
if(purchaseRequest.getPrice() > 30000){
System.out.println("请求编号为:" + purchaseRequest.getId() + "的请求被" + name + "处理了");
}else {
//不能则交给下个审批人处理
approver.process(purchaseRequest);
}
}
}
测试
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建请求
PurchaseRequest purchaseRequest = new PurchaseRequest(1,1200,1);
//创建相应的审批人
DepartMateApprover departMateApprover = new DepartMateApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("普副校长");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("石校长");
//需要将各个审批级别的审批人设置好(处理人构成环状)
departMateApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
schoolMasterApprover.setApprover(departMateApprover);
//测试,从级别低的开始审批
departMateApprover.process(purchaseRequest);
//输出结果
//请求编号为:1的请求被张主任处理了
}
}
2、职责链模式的注意事项和细节
将请求和处理分开,实现解耦,提高系统的灵活性
简化了对象,使对象不需要知道链的结构
性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web中Tomcat对Encoding的处理、拦截器
四、策略模式
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
1、介绍
**意图:**定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
**主要解决:**在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
**何时使用:**一个系统有许多许多类,而区分它们的只是他们直接的行为。
**如何解决:**将这些算法封装成一个一个的类,任意地替换。
**关键代码:**实现同一个接口。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
**注意事项:**如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
引出问题

问题解决

UML图
核心代码
/**
* 算法的提供者:鸭子飞翔的接口
*/
public interface FlyBehavior {
//飞翔技术咋样由具体子类实现
void fly();
}
/**
* 具体子类:飞翔技术高超
*/
public class GoodFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("飞翔技术高超~");
}
}
/**
* 具体子类:不会飞翔
*/
public class NoFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("不会飞翔~");
}
}
/**
* ClassName:GoodFlyBehavior
* Package:Strategy
* Description
*
* @Author:@wenxueshi
* @Create:2023/5/15 - 22:00
* @Version:v1.0
*/
/**
* 具体子类:飞翔技术一般
*/
public class BadFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("飞翔技术一般~");
}
}
/**
* ClassName:Duck
* Package:Strategy
* Description
*
* @Author:@wenxueshi
* @Create:2023/5/15 - 22:06
* @Version:v1.0
*/
/**
* 算法的使用者:鸭子类
*/
public abstract class Duck {
//属性,策略接口
FlyBehavior flyBehavior;
//可以添加其他策略接口
/**
* 显示鸭子信息
*/
public abstract void disPlay();
//动态设置某个对象的飞翔行为
public void setFlyBehavior(FlyBehavior flyBehavior){
this.flyBehavior = flyBehavior;
}
public void swim(){
System.out.println("鸭子游泳~");
}
public void fly(){
if(flyBehavior != null){
flyBehavior.fly();
}
}
}
/**
* ClassName:WildDuck
* Package:Strategy
* Description
*
* @Author:@wenxueshi
* @Create:2023/5/15 - 22:08
* @Version:v1.0
*/
/**
* 野鸭
*/
public class WildDuck extends Duck{
public WildDuck(){
//野鸭飞翔技术高超
flyBehavior = new GoodFlyBehavior();
}
@Override
public void disPlay() {
System.out.println("这是野鸭!");
}
}
/**
* ClassName:WildDuck
* Package:Strategy
* Description
*
* @Author:@wenxueshi
* @Create:2023/5/15 - 22:08
* @Version:v1.0
*/
/**
* 北京鸭
*/
public class PekingDuck extends Duck{
public PekingDuck(){
//北京鸭飞翔技术一般
flyBehavior = new BadFlyBehavior();
}
@Override
public void disPlay() {
System.out.println("这是北京鸭!");
}
}
/**
* ClassName:WildDuck
* Package:Strategy
* Description
*
* @Author:@wenxueshi
* @Create:2023/5/15 - 22:08
* @Version:v1.0
*/
/**
* 玩具鸭
*/
public class ToyDuck extends Duck{
public ToyDuck(){
//玩具鸭不会飞翔
flyBehavior = new NoFlyBehavior();
}
@Override
public void disPlay() {
System.out.println("这是玩具鸭!");
}
}
/**
* ClassName:Client
* Package:Strategy
* Description
*
* @Author:@wenxueshi
* @Create:2023/5/15 - 22:16
* @Version:v1.0
*/
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//野鸭
WildDuck wildDuck = new WildDuck();
wildDuck.fly();
//北京鸭
PekingDuck pekingDuck = new PekingDuck();
pekingDuck.fly();
//玩具鸭
ToyDuck toyDuck = new ToyDuck();
toyDuck.fly();
//动态设置玩具鸭的飞翔行为,玩具鸭原本不会飞,现在动态设置成可以飞
toyDuck.setFlyBehavior(new GoodFlyBehavior());
System.out.println("玩具鸭新设置的飞翔技能------------");
toyDuck.fly();
//输出结果
//飞翔技术高超~
//飞翔技术一般~
//不会飞翔~
//玩具鸭新设置的飞翔技能------------
//飞翔技术高超~
}
}
2、细节注意
策略模式的关键是:分析项目中变化部分与不变部分
策略模式的核心思想是:多用组合/聚合少用继承;用行为类组合,而不是行为的继承。更有弹性
体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if.else if..else)提供了可以替换继承关系的办法:策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
五、状态模式
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
介绍
**意图:**允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
**主要解决:**对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
**何时使用:**代码中包含大量与对象状态有关的条件语句。
**如何解决:**将各种具体的状态类抽象出来。
**关键代码:**通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。
应用实例: 1、打篮球的时候运动员可以有正常状态、不正常状态和超常状态。 2、曾侯乙编钟中,'钟是抽象接口','钟A'等是具体状态,'曾侯乙编钟'是具体环境(Context)。
优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
**注意事项:**在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
引出问题

UML图

核心代码
/**
* ClassName:State
* Package:State
* Description
*
* @Author:@wenxueshi
* @Create:2023/5/15 - 22:57
* @Version:v1.0
*/
/**
* 状态抽象类
*/
public abstract class State {
//扣除积分 -50
public abstract void deductMoney();
//是否中奖
public abstract boolean raffle();
//发放奖品
public abstract void distribute();
}
/**
* 不能抽奖状态
*/
public class NoRallfeState extends State{
//组合活动类
private Activity activity;
public NoRallfeState(Activity activity){
this.activity = activity;
}
/**
* 当我状态是不可以抽奖状态,扣除积分后,将状态设置为可以抽奖状态
*/
@Override
public void deductMoney() {
System.out.println("扣除积分成功,你可以抽奖了");
activity.setState(activity.getCanRallfeState());
}
/**
* 当前状态不能抽奖
*/
@Override
public boolean raffle() {
System.out.println("扣了积分才可以抽奖哦!");
return false;
}
/**
* 当前状态不能发放奖品
*/
@Override
public void distribute() {
System.out.println("不能发放奖品~");
}
}
/**
* 可以抽奖状态
*/
public class CanRallfeState extends State{
//组合活动类
private Activity activity;
public CanRallfeState(Activity activity){
this.activity = activity;
}
/**
* 已经扣取过积分了,不能再扣了
*/
@Override
public void deductMoney() {
System.out.println("已经扣取过积分了~");
}
/**
* 可以抽奖状态
*/
@Override
public boolean raffle() {
System.out.println("请稍后,正在抽奖!");
Random random = new Random();
int num = random.nextInt(10);
//10%中奖机会
if(num == 0){
//改变活动状态为发放奖品
activity.setState(activity.getDistriButeState());
return true;
}else {
System.out.println("很遗憾,没有抽中");
//将状态修改为不能抽奖状态
activity.setState(activity.getNoRallfeState());
return false;
}
}
/**
* 当前状态不能发放奖品
*/
@Override
public void distribute() {
System.out.println("没中奖,不能发放奖品~");
}
}
/**
* 发放奖品状态
*/
public class DistriButeState extends State{
//组合活动类
private Activity activity;
public DistriButeState(Activity activity){
this.activity = activity;
}
/**
*
*/
@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}
/**
* 当前状态不能抽奖
*/
@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}
/**
* 当前状态不能发放奖品
*/
@Override
public void distribute() {
if(activity.getCount() > 0){
System.out.println("恭喜中奖了~");
activity.setState(activity.getNoRallfeState());
}else {
System.out.println("很遗憾,奖品发完了!");
activity.setState(activity.getDistriButeOutState());
}
}
}
/**
* 奖品发放完毕状态
* 当我们把activity改变成DistriButeOutState,说明抽奖活动结束
*/
public class DistriButeOutState extends State{
//组合活动类
private Activity activity;
public DistriButeOutState(Activity activity){
this.activity = activity;
}
/**
*奖品发放完毕状态
*/
@Override
public void deductMoney() {
System.out.println("奖品发送完了,请下次再参加~");
}
/**
* 奖品发放完毕状态
*/
@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加~");
return false;
}
/**
* 奖品发放完毕状态
*/
@Override
public void distribute() {
System.out.println("奖品发送完了,请下次再参加~");
}
}
/**
* ClassName:Activity
* Package:State
* Description
*
* @Author:@wenxueshi
* @Create:2023/5/15 - 23:00
* @Version:v1.0
*/
/**
* 活动类
*/
public class Activity {
//表示当前活动的状态
State state = null;
//奖品数量
int count = 0;
//四种状态
State noRallfeState = new NoRallfeState(this);
State canRallfeState = new CanRallfeState(this);
State distriButeState = new DistriButeState(this);
State distriButeOutState = new DistriButeOutState(this);
//初始化当前状态和奖品数量
public Activity(int count){
this.state = getNoRallfeState();
this.count = count;
}
//扣除积分
public void deductMoney(){
state.deductMoney();
}
public void raffle(){
//如果抽奖成功
if(state.raffle()){
//则去领取奖品
state.distribute();
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public int getCount() {
int curCount = count;
count--;
return curCount;
}
public void setCount(int count) {
this.count = count;
}
public State getNoRallfeState() {
return noRallfeState;
}
public void setNoRallfeState(State noRallfeState) {
this.noRallfeState = noRallfeState;
}
public State getCanRallfeState() {
return canRallfeState;
}
public void setCanRallfeState(State canRallfeState) {
this.canRallfeState = canRallfeState;
}
public State getDistriButeState() {
return distriButeState;
}
public void setDistriButeState(State distriButeState) {
this.distriButeState = distriButeState;
}
public State getDistriButeOutState() {
return distriButeOutState;
}
public void setDistriButeOutState(State distriButeOutState) {
this.distriButeOutState = distriButeOutState;
}
}
测试
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建活动对象,奖品设置1个
Activity activity = new Activity(1);
//连续抽奖
for(int i = 0; i < 30; i++){
System.out.println("第--------" + (i+1) + "次抽奖--------");
//扣积分
activity.deductMoney();
//开始抽奖
activity.raffle();
}
}
}
六、备忘录模式
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。
介绍
**意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
**主要解决:**所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
**何时使用:**很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。
**如何解决:**通过一个备忘录类专门存储对象状态。
**关键代码:**客户不与备忘录类耦合,与备忘录管理类耦合。
应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctrl + z。 4、IE 中的后退。 5、数据库的事务管理。
优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。
**缺点:**消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
使用场景: 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。
注意事项: 1、为了符合迪米特原则,还要增加一个管理备忘录的类。 2、为了节约内存,可使用原型模式+备忘录模式。
引出问题

UML图

核心代码
/**
* 备忘录对象
*/
public class Memento {
//攻击力
private int vit;
//防御力
private int def;
public Memento(int vit, int def) {
this.vit = vit;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
/**
* 守护者对象,用来保存游戏角色的状态
*/
public class CareTaker {
//如果只保存一次状态
private Memento memento;
//对一个游戏角色保存多次状态
//private ArrayList<Memento> mementos;
//对多个游戏角色保存多次状态
//private HashMap<String,ArrayList<Memento>> roleMementos;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
/**
* 游戏角色
*/
public class GameRole {
private int vit;
private int def;
//创建Memento,根据当前的状态得到Memento
public Memento crateMemento(){
return new Memento(this.vit,this.def);
}
//从备忘录对象恢复到GameRole的状态
public void recoverGameRoleFromMemento(Memento memento){
this.vit = memento.getVit();
this.def = memento.getDef();
}
//显示当前角色的状态
public void disPlay(){
System.out.println("当前角色的攻击力为:" + this.vit + ",防御力为:" + this.def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
测试
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建游戏角色
GameRole gameRole = new GameRole();
gameRole.setVit(100);
gameRole.setDef(100);
System.out.println("和boss大战前的状态");
gameRole.disPlay();
//将当前状态保存到CareTaker中
CareTaker careTaker = new CareTaker();
careTaker.setMemento(gameRole.crateMemento());
System.out.println("和boss大战~");
gameRole.setVit(35);
gameRole.setDef(35);
gameRole.disPlay();
System.out.println("大战后,使用备忘录对象恢复到大战前");
gameRole.recoverGameRoleFromMemento(careTaker.getMemento());
gameRole.disPlay();
//输出结果
//和boss大战前的状态
//当前角色的攻击力为:100,防御力为:100
//和boss大战~
//当前角色的攻击力为:35,防御力为:35
//大战后,使用备忘录对象恢复到大战前
//当前角色的攻击力为:100,防御力为:100
}
}
七、中介者模式
中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。
介绍
**意图:**用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
**主要解决:**对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
**何时使用:**多个类相互耦合,形成了网状结构。
**如何解决:**将上述网状结构分离为星型结构。
**关键代码:**对象 Colleague 之间的通信封装到一个类中单独处理。
应用实例: 1、中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2、机场调度系统。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。
优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。
**缺点:**中介者会庞大,变得复杂难以维护。
使用场景: 1、系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。 2、想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
**注意事项:**不应当在职责混乱的时候使用。
引出问题

UML图

八、模板模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
介绍
**意图:**定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
**主要解决:**一些方法通用,却在每一个子类都重新写了这一方法。
**何时使用:**有一些通用的方法。
**如何解决:**将这些通用算法抽象出来。
**关键代码:**在抽象类实现,其他步骤在子类实现。
应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
**缺点:**每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
**注意事项:**为防止恶意操作,一般模板方法都加上 final 关键词。
引出问题

UML图

核心代码
/**
* 抽象类:表示豆浆
*/
public abstract class SoyaMilk {
//模板方法,用final修饰,防止子类覆盖
final void make(){
select();
//如果为true则顾客加配料
if (CustomWantConditions()){
addCondiments();
}
soak();
beat();
}
//选材料
void select(){
System.out.println("选择上好的黄豆");
}
//添加配料,由子类具体实现
abstract void addCondiments();
//浸泡
void soak(){
System.out.println("第三步,黄豆和配料开始浸泡,需要3小时");
}
//放到豆浆机中
void beat(){
System.out.println("第四步,黄豆和配料放到豆浆机中去打碎");
}
}
/**
* 红豆豆浆
*/
public class RedBeanSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {
System.out.println("添加上好的红豆");
}
}
/**
* 黑豆豆浆
*/
public class BlankBeanSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {
System.out.println("添加上好的黑豆");
}
}
测试类
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//制作红豆浆
System.out.println("----------制作红豆浆-------------");
RedBeanSoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
//制作红豆浆
System.out.println("------------制作黑豆浆--------------");
BlankBeanSoyaMilk blankBeanSoyaMilk = new BlankBeanSoyaMilk();
blankBeanSoyaMilk.make();
}
}
升级

在SoyaMilk中添加钩子方法
//钩子方法,顾客是否相加配料
boolean CustomWantConditions(){
return true;
}
修改make()方法
//模板方法,用final修饰,防止子类覆盖
final void make(){
select();
//如果为true则顾客加配料
if (CustomWantConditions()){
addCondiments();
}
soak();
beat();
}
添加纯豆浆类
/**
* 纯豆浆类
*/
public class SimpleBeanSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {
//空实现
}
//返回false不添加任何配料
@Override
boolean CustomWantConditions() {
return false;
}
}
修改测试类
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//制作红豆浆
System.out.println("----------制作红豆浆-------------");
RedBeanSoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
//制作红豆浆
System.out.println("------------制作黑豆浆--------------");
BlankBeanSoyaMilk blankBeanSoyaMilk = new BlankBeanSoyaMilk();
blankBeanSoyaMilk.make();
//制作纯豆浆
System.out.println("------------制作纯豆浆--------------");
SimpleBeanSoyaMilk simpleBeanSoyaMilk = new SimpleBeanSoyaMilk();
simpleBeanSoyaMilk.make();
}
}
九、访问者模式
在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
介绍
**意图:**主要将数据结构与数据操作分离。
**主要解决:**稳定的数据结构和易变的操作耦合问题。
**何时使用:**需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。
**如何解决:**在被访问的类里面加一个对外提供接待访问者的接口。
**关键代码:**在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。
**应用实例:**您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。
优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。
缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
使用场景: 1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
**注意事项:**访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
引出问题

UML图

核心代码
/**
* 评价抽象类
*/
public abstract class Action {
//得到男性的测评
abstract void getManResults(Man man);
//得到女性的测评
abstract void getWomanResults(Woman woman);
}
/**
* 具体评价类:评价成功
*/
public class Success extends Action{
@Override
void getManResults(Man man) {
System.out.println("男人给该歌手的评价是:很成功!");
}
@Override
void getWomanResults(Woman woman) {
System.out.println("女人给该歌手的评价是:很成功!");
}
}
/**
* 具体评价类:评价失败
*/
public class Fail extends Action{
@Override
void getManResults(Man man) {
System.out.println("男人给该歌手的评价是:晋级失败!");
}
@Override
void getWomanResults(Woman woman) {
System.out.println("女人给该歌手的评价是:晋级失败!");
}
}
/**
* 接待访问者抽象类
*/
public abstract class Person {
abstract void accept(Action action);
}
/**
* 接待访问者具体类
*/
public class Man extends Person{
@Override
void accept(Action action) {
action.getManResults(this);
}
}
/**
* 接待访问者具体类
*/
public class Woman extends Person{
@Override
void accept(Action action) {
action.getWomanResults(this);
}
}
/**
* 数据机构,维护很多人
*/
public class ObjectStructure {
private List<Person> personList = new ArrayList<>();
//添加
public void attach(Person person){
personList.add(person);
}
//移除
public void detach(Person person){
personList.remove(person);
}
//展示评价
public void disPlay(Action action){
for (Person person : personList) {
person.accept(action);
}
}
}
测试
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建ObjectStructure
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
//评价成功
Success success = new Success();
objectStructure.disPlay(success);
//评价失败
Fail fail = new Fail();
objectStructure.disPlay(fail);
//输出结果
//男人给该歌手的评价是:很成功!
//女人给该歌手的评价是:很成功!
//男人给该歌手的评价是:晋级失败!
//女人给该歌手的评价是:晋级失败!
}
}
十、迭代器模式
迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。
迭代器模式属于行为型模式。
介绍
**意图:**提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
**主要解决:**不同的方式来遍历整个整合对象。
**何时使用:**遍历一个聚合对象。
**如何解决:**把在元素之间游走的责任交给迭代器,而不是聚合对象。
**关键代码:**定义接口:hasNext, next。
**应用实例:**JAVA 中的 iterator。
优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
**缺点:**由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。
**注意事项:**迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
引出问题

UML图

核心代码
提供数据:
/**
* 系
*/
public class Department {
private String name;
private String description;
public Department(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
/**
* 接口
*/
public interface College {
String getName();
//添加系
void addDepartment(String name, String description);
//提供一个迭代器,用于遍历
Iterator createIterator();
}
/**
* 计算机学院
*/
public class ComputerCollege implements College{
//添加系
Department[] departments;
int departmentNum = 0; //系的个数
public ComputerCollege() {
departments = new Department[5];
addDepartment("java专业", "java专业");
addDepartment("php专业", "php专业");
addDepartment("大数据专业", "大数据专业");
}
@Override
public String getName() {
return "计算机学院";
}
//添加系
@Override
public void addDepartment(String name, String description) {
Department department = new Department(name, description);
departments[departmentNum++] = department;
}
//迭代器遍历操作
@Override
public Iterator createIterator() {
return new ComputerCollegeIterator(departments);
}
}
/**
* 信息工程学院
*/
public class InfoCollege implements College{
//添加系
List<Department> departmentList;
public InfoCollege() {
departmentList = new ArrayList<Department>();
addDepartment("信息安全", "信息安全");
addDepartment("网络安全", "网络安全");
addDepartment("服务器安全", "服务器安全");
}
@Override
public String getName() {
return "信息工程学院";
}
//添加系
@Override
public void addDepartment(String name, String description) {
Department department = new Department(name, description);
departmentList.add(department);
}
//迭代器遍历操作
@Override
public Iterator createIterator() {
return new InfoCollegeIterator(departmentList);
}
}
提供遍历方式
/**
* 计算机科学学院迭代器
*/
public class ComputerCollegeIterator implements Iterator {
//这里我们需要知道Department是以怎样的方式存放的=>数组
Department[] departments;
int position = 0; //遍历的位置
public ComputerCollegeIterator(Department[] departments) {
this.departments = departments;
}
//判断是否还有下一个元素
@Override
public boolean hasNext() {
if(position > departments.length || departments[position] == null){
return false;
}else {
return true;
}
}
//获取下一个元素
@Override
public Object next() {
Department department = departments[position];
position++;
return department;
}
//删除方法,空实现
public void remove(){
}
}
/**
* 信息工程学院迭代器
*/
public class InfoCollegeIterator implements Iterator {
//这里我们需要知道Department是以怎样的方式存放的=>集合
List<Department> departmentList;
int position = -1; //遍历的位置
public InfoCollegeIterator(List<Department> departmentList) {
this.departmentList = departmentList;
}
//判断是否还有下一个元素
@Override
public boolean hasNext() {
if(position >= departmentList.size() - 1){
return false;
}else {
position++;
return true;
}
}
//获取下一个元素
@Override
public Object next() {
return departmentList.get(position);
}
//删除方法,空实现
public void remove(){
}
}
输出
/**
* 输出实现
*/
public class OutPutImpl {
private List<College> collegeList;
public OutPutImpl(List<College> collegeList) {
this.collegeList = collegeList;
}
//打印学院
public void printCollege(){
Iterator<College> iterator = collegeList.iterator();
while (iterator.hasNext()){
College college = iterator.next();
System.out.println("========" + college.getName() + "========");
printDepartment(college.createIterator());
}
}
//打印系
public void printDepartment(Iterator iterator){
while (iterator.hasNext()){
Department department = (Department) iterator.next();
System.out.println(department.getName());
}
}
}
测试
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建学院集合
List<College> collegeList = new ArrayList<>();
//创建学院
ComputerCollege computerCollege = new ComputerCollege();
InfoCollege infoCollege = new InfoCollege();
//添加学院
collegeList.add(computerCollege);
collegeList.add(infoCollege);
//输出
OutPutImpl outPut = new OutPutImpl(collegeList);
outPut.printCollege();
//========计算机学院========
//java专业
//php专业
//大数据专业
//========信息工程学院========
//信息安全
//网络安全
//服务器安全
}
}
结束