admin 管理员组文章数量: 887016
设计模式的类型
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。当然,我们还会讨论另一类设计模式:J2EE 设计模式。
- 创建型模式-->对象怎么来
- 结构型模式-->对象和谁有关
- 行为型模式-->对象与对象在干嘛
- J2EE 模式-->对象合起来要干嘛(表现层,文中表示层个人感觉用的不准确)java是面向对象的语言,
设计模式的六大原则
1、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
简要概括:
- 开闭原则:实现热插拔,提高扩展性。
- 里氏代换原则:实现抽象的规范,实现子父类互相替换;
- 依赖倒转原则:针对接口编程,实现开闭原则的基础;
- 接口隔离原则:降低耦合度,接口单独设计,互相隔离;
- 迪米特法则,又称不知道原则:功能模块尽量独立;
- 合成复用原则:尽量使用聚合,组合,而不是继承;
工厂模式
1、介绍
工厂模式是一种对象创建型的设计模式,主要特点就是屏蔽对象创建细节,只暴露一个通用的创建接口。
主要解决:主要解决接口选择的问题。
何时使用:需要不同条件下创建不同子类的实例时。
如何解决:在工厂接口中添加对产品子类的支持,返回的也是一个抽象的产品。
关键代码:工厂方法中根据不同条件,实例化不同的子类进行返回。
应用实例: 1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。 2、Hibernate 换数据库只需换方言和驱动就可以。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要增加产品类和扩展工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和修改实现工厂,与开闭原则存在冲突。
使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
2、案例
2.1、背景
某客户想要购买宝马车,那么会联系宝马的4S店(工厂)。对于宝马的4S店来说,可以提供(生产)多款车型供客户选择(BMW 320、BMW 530,BMW 740)。对于客户来说,他不需要关心每款车型是怎么建造出来的,他需要做的,只是需要给4S店打个电话,说购买XX款车型。
2.2、实现
/**
* 抽象产品
*/
public class BMWCar
{
public void drive()
{
}
}
/**
* 具体产品BMW320
*/
public class BMW320 extends BMWCar
{
public void drive()
{
System.out.println("BMW320,运动酷炫。");
}
}
/**
* 具体产品BMW530
*/
public class BMW530 extends BMWCar
{
public void drive()
{
System.out.println("BMW530,时不我待。");
}
}
/**
* 具体产品BMW740
*/
public class BMW740 extends BMWCar
{
public void drive()
{
System.out.println("BMW740,高端商务。");
}
}
/**
* 宝马工厂,覆盖所有宝马车型的构造方法
*/
public class BMWFactory
{
public BMWCar getBMWCar(String type)
{
if ("BMW320".equals(type))
{
return new BMW320();
}
else if ("BMW530".equals(type))
{
return new BMW530();
}
else if ("BMW740".equals(type))
{
return new BMW740();
}
else
{
return null;
}
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
BMWFactory bmwFactory = new BMWFactory();
BMWCar bmwCar1 = bmwFactory.getBMWCar("BMW320");
bmwCar1.drive();
BMWCar bmwCar2 = bmwFactory.getBMWCar("BMW530");
bmwCar2.drive();
BMWCar bmwCar3 = bmwFactory.getBMWCar("BMW740");
bmwCar3.drive();
}
}
运行结果如下图:
2.3、扩展
对于工厂类中的方法,if-else太低端。如果有比较多的车型,写起来比较麻烦。虽然也可以使用switch,但是依然比较繁琐。根据名称实例化不同的类,这个场景就适合使用Java中的反射啦,重写工厂的逻辑如下:
/**
* 宝马工厂,覆盖所有宝马车型的构造方法
*/
public class BMWFactory
{
public BMWCar getBMWCar(String type) throws ClassNotFoundException,
IllegalAccessException, InstantiationException
{
Class cl = Class.forName(type);
return (BMWCar)cl.newInstance();
}
}
抽象工厂模式
1、介绍
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
主要解决:主要解决接口选择的问题。
何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
如何解决:在一个产品族里面,定义多个产品。每个具体的工厂负责一个产品族。抽象工厂的返回值为最高级抽象产品。
关键代码:在一个工厂里聚合多个同类产品(在同一个产品族中)。
应用实例:举一个衣服与衣柜的例子。家里边,有男装(产品族,其中包含休闲男装和商务男装)、女装(产品族,其中包含休闲女装和商务女装)。商务女装、商务男装、时尚女装、时尚男装,这些都是具体产品。男装专门放在男衣柜(具体工厂)中,女装专门放在女衣柜(具体工厂)中。当我们需要拿衣服时候,从衣柜(抽象工厂)中获取。
所以抽象工厂,非常适合解决两个维度的组合产品的构造问题,取其中一个维度作为产品族,另外一个维度作为产品族中具体的多个产品。
优点:能够从多个产品族的多个产品中,简洁的获取想要的具体产品。解决了工厂模式中的不符合开闭原则的问题(增加新的产品时候,不修改工厂,而是增加工厂)。
缺点:产品族扩展比较困难,要增加一个系列的某一产品,要增加具体的产品类,还要增加对应的工厂类(或者修改对应产品族的工厂类)。
注意事项:产品族难扩展,产品等级易扩展。
2、案例
2.1、背景
还是举买车的例子。
某客户想要购买一辆车,他要联系4S店,首先得有4S店(抽象工厂)的电话。
客户上网查询(建造工厂),发现了宝马4S店(具体工厂)的电话和奔驰4S店(具体工厂)的电话。
客户拨通了宝马4S店的电话(获取具体工厂),发现目前店里可以提供(生产)多款车型(具体产品)供客户选择(BMW 320、BMW 530,BMW 740)。
客户拨通了奔驰4S店的电话(获取具体工厂),发现目前店里可以提供(生产)多款车型(具体产品)供客户选择(BenzC200、BenzE300)。
2.2、实现
汽车类
/**
* 最高级抽象产品,用于抽象工厂的建造方法的返回值
*/
public abstract class Car
{
abstract void drive();
}
宝马产品类
/**
* 抽象产品
*/
public abstract class BMWCar extends Car
{
}
/**
* 具体产品BMW320
*/
public class BMW320 extends BMWCar
{
public void drive()
{
System.out.println("BMW320,运动酷炫。");
}
}
/**
* 具体产品BMW530
*/
public class BMW530 extends BMWCar
{
public void drive()
{
System.out.println("BMW530,时不我待。");
}
}
/**
* 具体产品BMW740
*/
public class BMW740 extends BMWCar
{
public void drive()
{
System.out.println("BMW740,高端商务。");
}
}
奔驰产品类
/**
* 抽象产品
*/
public abstract class BenzCar extends Car
{
}
/**
* 具体产品C200
*/
public class BenzC200 extends BenzCar
{
public void drive()
{
System.out.println("BenzC200,实惠有面");
}
}
/**
* 具体产品E300
*/
public class BenzE300 extends BenzCar
{
public void drive()
{
System.out.println("BenzE300,商务气派");
}
}
工厂类
/**
* 奔驰工厂,覆盖所有奔驰车型的构造方法
*/
public class BenzFactory extends AbstractFactory
{
public Car getCar(String type) throws ClassNotFoundException,
IllegalAccessException, InstantiationException
{
Class cl = Class.forName(type);
return (BenzCar)cl.newInstance();
}
}
/**
* 宝马工厂,覆盖所有宝马车型的构造方法
*/
public class BMWFactory extends AbstractFactory
{
public Car getCar(String type) throws ClassNotFoundException,
IllegalAccessException, InstantiationException
{
Class cl = Class.forName(type);
return (BMWCar)cl.newInstance();
}
}
抽象工厂类
public abstract class AbstractFactory
{
public abstract Car getCar(String type) throws ClassNotFoundException,
IllegalAccessException, InstantiationException;
}
超级工厂类
/**
* 超级工厂类,建造工厂的工厂
*/
public class FactoryProducer
{
public static AbstractFactory getFactory(String type)
throws IllegalAccessException, InstantiationException, ClassNotFoundException
{
Class cl = Class.forName(type);
System.out.println("创建工厂"+type);
return (AbstractFactory)cl.newInstance();
}
}
验证
/**
* 验证
*/
public class Demo
{
public static void main(String[] args) throws IllegalAccessException,
InstantiationException, ClassNotFoundException
{
AbstractFactory abstractFactory = FactoryProducer.getFactory("BMWFactory");
Car bmwCar = abstractFactory.getCar("BMW320");
bmwCar.drive();
Car bmwCar1 = abstractFactory.getCar("BMW530");
bmwCar1.drive();
Car bmwCar2 = abstractFactory.getCar("BMW740");
bmwCar2.drive();
AbstractFactory abstractFactory1 = FactoryProducer.getFactory("BenzFactory");
Car benzCar = abstractFactory1.getCar("BenzC200");
benzCar.drive();
Car benzCar1 = abstractFactory1.getCar("BenzE300");
benzCar1.drive();
}
}
运行结果如下如所示:
3、总结
抽象工厂模式非常针对2个维度描述的产品的构造问题。取其中一个维度作为产品族(也就是对应一个具体工厂),另外一个维度作为产品族下的具体产品。从这个角度说,抽象工厂模式是在工厂模式基础上,做了一个维度的升级。
举例,比如商务男装,商务女装,时尚男装,时尚女装的选择问题。取男装为一个产品族,对应一个工厂;取女装作为一个产品族,对应一个工厂。每个工厂中会生产多种类型(商务或者时尚)的衣服。
单例模式
1、介绍
单例模式是一种创建型模式。该模式中,构造方法是私有的,类内自己创建实例化对象,并返回给外部调用。通过加锁能够保证该类只有一个实例化对象,节省系统资源和开销。
意图:保证一个类仅有一个实例,提供给外部调用。
主要解决:避免类被频繁地创建与销毁,增加性能消耗;避免内存中保存多份实例,增加内存开销。
何时使用:需要控制实例数目,节省系统资源的场景。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数私有化,类内自己构造实例,
应用实例: 1、Windows的Task Manager(任务管理器) 2、多线程的线程池 3、网站的计数器
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
注意事项:getInstance() 方法需要使用synchronized关键字保证线程安全,避免多线程场景下实例化多个对象。
2、案例实现
在具体实现上,单例模式存在几种不同的实现方式。
方式一:Lazy loading + 非线程安全
实例化的动作在getInstance()
函数中进行,属于Lazy loading。不使用synchronized
关键字,所以是非线程安全的。
/**
* Singleton.java
*/
public class Singleton
{
private static Singleton singleton;
public static Singleton getInstance()
{
if (singleton == null)
{
singleton = new Singleton();
}
return singleton;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0
方式二:Lazy loading + 线程安全
实例化的动作在getInstance()
函数中进行,属于Lazy loading。使用synchronized
关键字,所以是线程安全的。
/**
* Singleton.java
*/
public class Singleton
{
private static Singleton singleton;
public static synchronized Singleton getInstance()
{
if (singleton == null)
{
singleton = new Singleton();
}
return singleton;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0
方式三:非Lazy loading + 线程安全
实例化的动作在singleton
定义时进行,所以是在类加载时生成对象,直接占用内存空间。这种使用方式不需要加锁,所以执行效率比方式二要高。
/**
* Singleton.java
*/
public class Singleton
{
private static Singleton singleton = new Singleton();
public static synchronized Singleton getInstance()
{
return singleton;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0
方式四:双重检查锁定
在getInstance()
方法中,判断当singleton
为空需要实例化的时候再进行加锁,减小了锁的粒度,相对于直接加锁性能上会更高。
new Singleton()
操作是非原子的。所以用 volatile
关键字修饰singleton
。volatile
关键字影响内存可见性,保证线程对被修饰变量的所有更改都直接映射到JMM的主内存中,并且保证操作的有序性。在多线程环境下,如果不加volatile
,那么指令的执行次序是存在乱序的。对于new Singleton()
操作,最终翻译成字节码,是包含多条指令的。如果线程A和线程B都访问到单例,线程A获取到锁,在进行单例的初始化。由于指令的乱序执行,有可能是先分配了空间(这时候instance
的值已经不是null
了),再执行其他指令进行初始化。如果在分配完空间之后,其他指定执行之前,线程A被踢出了CPU,这时候线程B执行,检查内存中instance
已经不是null
,则获取并使用。但是此时instance
并没有初始化完成,导致程序错误。
/**
* Singleton.java
*/
public class Singleton
{
private static volatile Singleton singleton;
public static Singleton getInstance()
{
if (singleton == null)
{
synchronized (Singleton.class)
{
if (singleton == null)
{
singleton = new Singleton();
}
}
}
return singleton;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0
方式五:静态内部类
Singleton类被装载了,instance不一定被初始化。因为InnerSingleton类没有被主动使用,所以这种方式也具有Lazy loading的效果。
/**
* Singleton.java
*/
public class Singleton
{
private static class InnerSingleton
{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance()
{
return InnerSingleton.INSTANCE;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0
建造者模式
1、介绍
建造者模式是一种创建型模式,主要特点是通过逐步添加单体对象形成一个复杂的对象。
主要解决:向复杂对象中添加多个单体对象的问题。复杂对象类似一个集合,包含多个单体对象,所以存在非常多的变化。单体对象则为确定的不变的对象。通过建造者模式,将变与不变分开
如何解决:复杂对象类似一个容器,向其中添加单体对象。对于单体对象,最高层实现相同的Item接口(即所有的具体的对象具有共同的特点)。
关键代码:所有单体对象具有相同特点(实现相同接口)。建造者的建造方法。
应用实例: 1、Java中StringBuilder的实现。2、肯德基点餐,餐对象中包含薯条汉堡等元素对象无限组合。3、淘宝购物车中添加商品。
优点: 建造者只需要关注建造方法,单一职责。
缺点:单体产品必须具有某些相同的特点。
2、实现
2.1、背景
这里举一个购物的例子。光棍节已经变成了购物的盛会,用户会向购物车(复杂对象)中添加(构建)各种商品(单体对象),然后在1111当天进行结算并显示所有订单的信息(单体对象的共同特征)。当用户(建造者)在购物的时候,不需要关注商品是如何组合的,而只需要向购物车进行添加(构建)即可。
2.2、实现
1)单体对象接口(单体对象共同点)定义
public interface Item {
String getName();
float getPrice();
String getUnit();
String getExpress();
}
2)单体对象定义
public class Milk implements Item
{
@Override
public String getName() {
return "牛奶";
}
@Override
public float getPrice() {
return 5f;
}
@Override
public String getUnit() {
return "盒";
}
@Override
public String getExpress() {
return "顺丰快递";
}
}
public class Bread implements Item
{
@Override
public String getName() {
return "面包";
}
@Override
public float getPrice() {
return 3f;
}
@Override
public String getUnit() {
return "个";
}
@Override
public String getExpress() {
return "圆通快递";
}
}
public class Desk implements Item
{
@Override
public String getName() {
return "桌子";
}
@Override
public float getPrice() {
return 120f;
}
@Override
public String getUnit() {
return "张";
}
@Override
public String getExpress() {
return "韵达快递";
}
}
public class Cabbage implements Item
{
@Override
public String getName() {
return "大白菜";
}
@Override
public float getPrice() {
return 0.5f;
}
@Override
public String getUnit() {
return "颗";
}
@Override
public String getExpress() {
return "申通快递";
}
}
3)复杂对象定义
import java.util.ArrayList;
import java.util.List;
public class ShoppingCart
{
private List<Item> cart = new ArrayList<>();
public ShoppingCart add(Item item)
{
cart.add(item);
return this;
}
public void printCost()
{
float totalCost = 0f;
for (Item item : cart)
{
totalCost += item.getPrice();
}
System.out.println("总价:" + totalCost);
}
public void printItem()
{
for (Item item : cart)
{
System.out.println("名称:" + item.getName());
System.out.println("单价:" + item.getPrice());
System.out.println("规格:" + item.getUnit());
System.out.println("快递:" + item.getExpress());
System.out.println();
}
}
}
4)测试代码
public class Test
{
public static void main(String[] args)
{
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.add(new Milk()).add(new Bread());
shoppingCart.printItem();
shoppingCart.printCost();
}
}
5)运行结果
名称:牛奶
单价:5.0
规格:盒
快递:顺丰快递名称:面包
单价:3.0
规格:个
快递:圆通快递总价:8.0
Process finished with exit code 0
原型模式
1、介绍
原型模式是一种创建型的设计模式,主要的特点是通过克隆已有的对象来进行创建,而不需要通过new操作(甚至是一项一项的设置属性)来实现。原型模式特别适合于构造对象成本较大的场景或者对性能要求较高的场景。
主要解决:在运行期建立对象。
使用场景:对象的构造过程比较复杂,成本较高。对性能要求比较高的场景。
关键技术:实现Cloneable
接口,定义clone
方法。
注意事项: 1. 原型模式属于浅拷贝,只能拷贝基础变量;2. clone方法是直接进行内存拷贝,不会调用对象的构造方法。如果对象的构造方法是私有的,也是可以进行clone的。
其实这里的定义clone
方法,相当于是重写父类(Object)的clone
,在下边的代码案例中可以看出。而Object的clone
方法,则是native方法。
// Object.java
protected native Object clone() throws CloneNotSupportedException;
2、实现
2.1、背景
这次用一个动漫的场景来说明原型模式的实现过程,那就是B站动漫的镇站之作品《某科学的超电磁炮》(至于为何是镇站之作,请自行百度)。在动漫中,主角御坂美琴(原始对象),白井黑子和一方通行等人,都具有各自的超能力。在学园都市中想要重新出现一个相同超能力的人毕竟非常困难,但是却可以通过克隆(实现clone方法)的方式来进行制造。而剧情当中就是这样,研究机构获取到了御坂美琴的基因,通过克隆方式制造了非常多的克隆体(克隆对象)来进行使用,而不是把真正的御坂美琴喊过来。
2.2、实现
1)定义角色的抽象类
实现Cloneable接口,并且定义clone
方法。
public abstract class RoleInRailGun implements Cloneable
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
abstract String hasSkill();
public Object clone()
{
Object clone = null;
try
{
clone = super.clone();
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
return clone;
}
}
2)定于具体的角色
//白井黑子
public class ShiraiKuroko extends RoleInRailGun
{
public ShiraiKuroko()
{
setName("ShiraiKuroko");
}
@Override
String hasSkill()
{
return "空间转换";
}
}
// 御坂美琴
public class MisakaMikoto extends RoleInRailGun
{
public MisakaMikoto()
{
setName("MisakaMikoto");
}
@Override
String hasSkill()
{
return "炮儿姐无敌,超电磁炮";
}
}
public class Accelerator extends RoleInRailGun
{
public Accelerator()
{
setName("Accelerator");
}
@Override
String hasSkill()
{
return "控制矢量";
}
}
3) 定义克隆工厂
import java.util.HashMap;
public class RoleBuilder
{
private static HashMap<String, RoleInRailGun> roles = new HashMap<String, RoleInRailGun>();
public static void buildRoles()
{
MisakaMikoto misakaMikoto = new MisakaMikoto();
roles.put("MisakaMikoto", misakaMikoto);
ShiraiKuroko shiraiKuroko = new ShiraiKuroko();
roles.put("ShiraiKuroko", shiraiKuroko);
Accelerator accelerator = new Accelerator();
roles.put("Accelerator", accelerator);
}
public static RoleInRailGun getRole(String name)
{
if (!roles.containsKey(name))
{
return null;
}
RoleInRailGun role = roles.get(name);
System.out.println("Role is " + role.getName());
System.out.println("Origin object address is " + role.toString());
RoleInRailGun role_copy = (RoleInRailGun) role.clone();
System.out.println("Origin object address is " + role_copy.toString());
System.out.println("");
return role_copy;
}
}
4) 进行验证
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
RoleBuilder.buildRoles();
RoleInRailGun roleInRailGun1 = RoleBuilder.getRole("MisakaMikoto");
RoleInRailGun roleInRailGun2 = RoleBuilder.getRole("ShiraiKuroko");
}
}
5)运行结果
Role is MisakaMikoto
Origin object address is MisakaMikoto@43a25848
Origin object address is MisakaMikoto@3ac3fd8bRole is ShiraiKuroko
Origin object address is ShiraiKuroko@5594a1b5
Origin object address is ShiraiKuroko@6a5fc7f7Process finished with exit code 0
从运行结果可以看到,原对象和克隆对象是完全的两个对象,两者的内存地址是不同的。
适配器模式
1、介绍
适配器模式属于一种结构型的设计模式,如同名字一样,用于实现两个原本不相关的接口的连通性,即进行适配。通过适配器模式,可以扩展类的功能,使其拥有其他类的功能
适配器模式的使用场景,凡是能联系到适配这两个字的,一般都是适用的。比如说,电压适配器,通过在电源插排上接上一个电压适配器,我们的手机就可以进行充电了。比如说,读卡器,通过在电脑的USB接口上插上一个读卡器,我们的TF/SD/CF等内存卡就可以使用了。
适配器模式的实现方法,主要有继承和依赖。由于依赖关系具有更好的灵活性,而且可以一个类可以实现对多个类的依赖,所以出场率更高一些。
以依赖为例,在具体实现中,
- 主体类依赖适配器类,两者实现相同的接口。所以对于某个动作或者方法,当主体能够完成的时候就自己完成,如果主体完成不了,就用适配器类来完成。
- 在适配器类中,则依赖了其他的类(假设名字为A)。在适配器的对应方法中,执行的是A类的相关方法。
所以通过适配器类,将原本不存在关联的主体类和A类联系起来了。
2、案例
2.1、背景
在使用笔记本电脑的时候,我们可以查看保存在电脑硬盘上的电影。但是也经常会从朋友电脑上copy过来一些电影资源,但是碰巧,当时手头上没有U盘可以用,那么就用TF卡和SD卡进行拷贝拿了过来。通过一个读卡器(适配器),我们可以把原本没有相同接口的两个类联系起来。电脑和读卡器都具有相同的USB接口,将读卡器插到电脑上,然后把对应的内存卡插进读卡器中,就可以观看内存卡中的电影了。电脑和读卡器是依赖关系(电脑使用读卡器),读卡器与TF/SD卡也是依赖关系(读卡器使用TF/SD卡)。
对应的UML类图如下:
2.2、实现
1)电脑接口
public interface ComputerInterface
{
void readFilesFromHardDisk(String diskType, String fileName);
}
2)内存卡接口
public interface CardInterface
{
void readFilesFromFlashMemory(String diskType, String fileName);
}
3)内存卡实体类
public class SDCard implements CardInterface
{
@Override
public void readFilesFromFlashMemory(String diskType, String fileName)
{
System.out.println("从SD卡放映电影:" + fileName);
}
}
public class TFCard implements CardInterface
{
@Override
public void readFilesFromFlashMemory(String diskType, String fileName)
{
System.out.println("从TF卡放映电影:" + fileName);
}
}
4)适配器类
public class CardAdaptor implements ComputerInterface
{
CardInterface cardInterface;
@Override
public void readFilesFromHardDisk(String diskType, String fileName)
{
if ("SDCard".equals(diskType))
{
cardInterface = new SDCard();
}
else
{
cardInterface = new TFCard();
}
cardInterface.readFilesFromFlashMemory(diskType, fileName);
}
}
5)电脑类
public class Computer implements ComputerInterface
{
CardAdaptor cardAdaptor;
@Override
public void readFilesFromHardDisk(String diskType, String fileName)
{
if ("hardDisk".equals(diskType))
{
System.out.println("从硬盘放映电影:" + fileName);
}
else if ("TFCard".equals(diskType) || "SDCard".equals(diskType))
{
cardAdaptor = new CardAdaptor();
cardAdaptor.readFilesFromHardDisk(diskType, fileName);
}
else
{
System.out.println("参数非法");
}
}
}
6)验证代码
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Computer computer = new Computer();
computer.readFilesFromHardDisk("hardDisk", "《乱世佳人》");
computer.readFilesFromHardDisk("TFCard", "《肖申克的救赎》");
computer.readFilesFromHardDisk("SDCard", "《教父》");
}
}
6)运行结果
从硬盘放映电影:《乱世佳人》
从TF卡放映电影:《肖申克的救赎》
从SD卡放映电影:《教父》Process finished with exit code 0
3、总结
凡是能与适配两个字沾边的,都可以使用适配器模式
适配器模式的优点和缺点都很明显。优点是通过适配器,联系原本不相关的类,打通二者的接口。缺点是如果在一个工程中大量使用适配器模式,对导致代码可读性变差,逻辑变得复杂。
桥接模式
1、介绍
桥接模式是一种结构型的设计模式,主要是特点是将抽象部分与实现部分分离开来,从而能够进行独自的变化。在桥接模式中,所谓的桥,个人理解是在抽象层中,将接口联系到一个抽象类中,更确切的说,是依赖关系。桥接模式主要适用场景是,某个对象需要从两个或者多个(一般是两个)维度进行描述或者操作的时候,能够简洁的进行处理。
特点:抽象与实现分离。抽象层依赖,具有非常好的扩展性。
适用场景:当场景中某个事物需要从两个或者多个维度进行描述或者操作。
2、案例
2.1、情景
举一个在饭店(抽象类)吃饭(动作接口)的例子。我们可以选择在香格里拉(具体类)吃饭,也可以在希尔顿(具体类)吃饭。两个酒店都可以提供烤肉(吃烤肉,动作接口实现类)和沙拉(吃沙拉,动作接口实现类)。我们的操作或者描述的问题,就是在什么酒店吃什么的问题。很显然,这是一个2维度的选择组合的动作,那么就可以通过桥接模式的思路来进行实现。
对应的UML类图如下:
2.2、实现
1)吃饭接口
public interface EatFood
{
String eat();
}
2)吃饭实现类
public class EatMeat implements EatFood
{
@Override
public String eat()
{
return "烤肉";
}
}
public class EatSalad implements EatFood
{
@Override
public String eat()
{
return "沙拉";
}
}
3)餐厅抽象类
public abstract class Restaurant
{
protected EatFood eatFood;
public Restaurant(EatFood eatFood)
{
this.eatFood = eatFood;
}
public abstract String action();
}
4)餐厅实体类
public class ShangriLa extends Restaurant
{
public ShangriLa(EatFood eatFood)
{
super(eatFood);
}
@Override
public String action()
{
return "在香格里拉酒店吃" + eatFood.eat();
}
}
public class Healton extends Restaurant
{
public Healton(EatFood eatFood)
{
super(eatFood);
}
@Override
public String action()
{
return "在希尔顿酒店吃" + eatFood.eat();
}
}
5)验证程序
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Restaurant restaurant = new ShangriLa(new EatMeat());
System.out.println(restaurant.action());
Restaurant restaurant1 = new Healton(new EatSalad());
System.out.println(restaurant1.action());
}
}
6)运行结果
在香格里拉酒店吃烤肉
在希尔顿酒店吃沙拉Process finished with exit code 0
3、总结
3.1、与抽象工厂的异同
上文讲到,桥接模式的适用场景是需要从两个或者多个维度去描述或者操作事物。而抽象工厂模式也与“多个维度”相关。这两种模式的适用场景都是涉及多维度的。但是是存在区别的,抽象工厂模式更关注对象的创建,是创建型设计模式。而桥接模式则是关注对象创建之后的动作或者功能。所以两者的区别是比较明显的。
3.2、桥接模式特点
桥接模式的特点是将抽象与实现分离,在抽象层建立依赖关系,从而两个维度上的实现类可以进行各自的变化不会相互影响,具有非常好的扩展性。
组合模式
1、介绍
组合模式是一种结构型的设计模式。组合模式中,对象具有一个List,用于保存其他的同类对象,从而形成了一个树形的结构,表示整体与部分的关系。在树形结构中,枝干节点和叶子节点会实现相同的接口(或者就是同一个类),从而在外部使用该对象时候,无需关注树形结构内部的细节,而可以统一的进行调用。
组合模式的使用场景,就是当出现树形结构,而且不需要关注复杂对象内部细节,只需要把复杂对象当作单一对象处理的时候。具体场景例如,二级部门是一级部门的子部门,挂靠在其下;操作系统中,文件是存放在对应的目录下,而目录其实也是一种文件。
2、案例
2.1、背景
以上文中提到的部门情况为例。一级部门下边还存在多个二级部门,而所有的这些部门,都具有各自的一些信息,在主方法中来将部门的基本信息进行打印。我们的部门类,会包含本部门的某些特征信息,还会包含一个部门的集合,用户保存其负责的其他子部门。
2.2、实现
1)部门类
import java.util.ArrayList;
import java.util.List;
public class Department
{
private String name;
private int numberOfPeople;
private String nameOfMinister;
private int level;
public List<Department> getSubDepartments() {
return subDepartments;
}
public void setSubDepartments(List<Department> subDepartments) {
this.subDepartments = subDepartments;
}
private List<Department> subDepartments;
public Department(String name, int numberOfPeople, String nameOfMinister, int level) {
this.name = name;
this.numberOfPeople = numberOfPeople;
this.nameOfMinister = nameOfMinister;
this.level = level;
this.subDepartments = new ArrayList<Department>();
}
public void add(Department department)
{
this.subDepartments.add(department);
}
public void remove(Department department)
{
this.subDepartments.remove(department);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumberOfPeople() {
return numberOfPeople;
}
public void setNumberOfPeople(int numberOfPeople) {
this.numberOfPeople = numberOfPeople;
}
public String getNameOfMinister() {
return nameOfMinister;
}
public void setNameOfMinister(String nameOfMinister) {
this.nameOfMinister = nameOfMinister;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
}
2)验证
public class Test
{
public static void main(String[] args)
{
Department department_level1 = new Department("消费者BG",1000,
"zhang_san",1);
department_level1.add(new Department("产品线1",300,
"li_si",2));
department_level1.add(new Department("产品线2",300,
"wang_wu",2));
department_level1.add(new Department("产品线3",400,
"zhao_liu",2));
printDep(department_level1);
}
public static void printDep(Department department)
{
System.out.println("部门名称:" + department.getName());
System.out.println("部门人数:" + department.getNumberOfPeople());
System.out.println("部长名称:" + department.getNameOfMinister());
System.out.println("部门级别:" + department.getLevel());
System.out.println("--------------------------");
if (!department.getSubDepartments().isEmpty())
{
for (Department department1 : department.getSubDepartments())
{
printDep(department1);
}
}
}
}
3)运行结果
部门名称:消费者BG
部门人数:1000
部长名称:zhang_san
部门级别:1 部门名称:产品线1
部门人数:300
部长名称:li_si
部门级别:2 部门名称:产品线2
部门人数:300
部长名称:wang_wu
部门级别:2 部门名称:产品线3
部门人数:400
部长名称:zhao_liu
部门级别:2Process finished with exit code 0
3、总结
- 优点:复杂对象简单化,使用方便,易于进行节点的扩展
装饰器模式
1、介绍
装饰器模式是一种结构型的设计模式。使用该模式的目的是为了较为灵活的对类进行扩展,而且不影响原来类的结构。有同学说可以通过继承的方式进行实现啊,没错,继承的确可以实现,但是继承的成本相对比较高,而且如果涉及到扩展多个功能的话,继承实现起来的成本较大。
装饰器模式的思想是,对原来的类进行一层封装,通过依赖实现扩展。但是装饰器的依赖有两个比较重要的特点。
第一,装饰器的抽象类,依赖的是原来类的接口。装饰器的实现类与原本的类之间,不会存在耦合的现象,两个类可以各自进行对应的变化。这一点与桥接模式是相同的。
第二,装饰器的抽象类会同时继承原来类的接口,从而保证装饰器的实现类具有与原来的类一样的方法,只是装饰器实现类的方法有新的扩展。可以理解为对原有类的某种功能或者特性的加强。这样产生的效果就是,从使用者的角度来看,会更加的便捷,因为原来的类与装饰器类具有一样的方法,真是其中实际的功能和作用进行了加强。
装饰器模式的使用场景,抽象来说就是需要扩展类但是又不想引入子类,或者也可以根据字面的含义,能与装饰或者功能加强有关的场景。比如说,一张画,是可以欣赏的,但是没有办法挂到墙上。那么我们可以找一个相框(装饰器实现类),将画放到相框中,之后就可以把这个带相框的画挂到墙上了。再比如说,我们买了一张床,就可以在上边睡觉了。但是我们希望睡的更加舒服,所以需要在床上增加一个装饰,比如垫上床垫,加上枕头,盖上床单。装饰之后,它依然有承载睡觉的属性。
2、案例
2.1、背景
就举一个上文提到的画与画框的例子。为了提高节操,哦不,是情操,本同学需要观赏(接口)一下工艺品。谈到观赏,我们可以观赏油画和水墨画(接口实现类)。但是油画和水墨画无法挂在墙上让众人欣赏,所以我们希望找一个相框(装饰器抽象类),将画放进去,然后挂起供人欣赏(装饰或者功能加强)。之后,我们终于找到了一个木质的相框(装饰器实现类),装饰之后,具有更好的观赏效果。
2.2、实现
1)定义观赏的接口
public interface Look {
public String LookAtPic();
}
2)定义观赏的实现类
public class InkPic implements Look
{
@Override
public String LookAtPic()
{
return "欣赏水墨画";
}
}
public class OilPic implements Look
{
@Override
public String LookAtPic() {
return "欣赏油画";
}
}
3)定义装饰器抽象类
public abstract class PicDecorator implements Look
{
private Look look;
public Look getLook() {
return look;
}
public void setLook(Look look) {
this.look = look;
}
public PicDecorator(Look look) {
this.look = look;
}
@Override
public abstract String LookAtPic();
}
4)装饰器实现类
public class WoodPicDecorator extends PicDecorator
{
public WoodPicDecorator(Look look) {
super(look);
}
@Override
public String LookAtPic() {
return this.getLook().LookAtPic() + ",木质相框";
}
}
5)运行
public class Test
{
public static void main(String[] args)
{
OilPic oilPic = new OilPic();
System.out.println(oilPic.LookAtPic());
WoodPicDecorator woodPicDecorator = new WoodPicDecorator(oilPic);
System.out.println(woodPicDecorator.LookAtPic());
InkPic inkPic = new InkPic();
System.out.println(inkPic.LookAtPic());
woodPicDecorator = new WoodPicDecorator(inkPic);
System.out.println(woodPicDecorator.LookAtPic());
}
}
5)结果
欣赏油画
欣赏油画,木质相框
欣赏水墨画
欣赏水墨画,木质相框Process finished with exit code 0
3、总结
装饰器模式的关键代码有两点:第一,装饰器抽象类依赖原接口;第二,装饰器抽象类实现原接口。
优点:低成本扩展类的方法(功能加强)
缺点:这种方法非常精巧,非要找一个缺点的话,那就是精巧的东西一般都不太好理解。
装饰器,桥接和适配器模式的异同
介绍
前面几篇文章分别介绍了装饰器模式,桥接模式和适配器模式这三种设计模式。这三种设计模式,从设计思想角度上看是非常相似的。三者都是结构型的设计模式,而且都存在依赖抽象的情况。但是三者之间却又存在一些微妙的区别,这也是本文重点关注的内容。
适配器模式
该模式重点强调的是适配的功能。
该模式的关键点是:
- 主体类和适配器类实现相同的接口A
- 主体类依赖适配器类
- 适配器类依赖抽象接口B
- 被适配的类实现抽象接口B
最终的效果就是,主体类可以使用之前不相关的被适配类中的某些功能。
把前文《Java设计模式(6)----------适配器模式》中的UML图拿过来,结构是这样的
桥接模式
该模式重点强调的是多维度的变化。
该模式的关键点是:
- 主体类依赖抽象A
- 主体类具有多个不同的实现类
- 抽象A具有多个不同的实现类
最终的效果就是,主体类的实现类和抽象的实现类分别可以在两个维度上进行各自的变化。如果主体类依赖多个抽象,则维度进行增加,方便扩展。
把前文Java设计模式(7)----------桥接模式中的UML图拿过来,结构是这样的
装饰器模式
该模式重点强调的是装饰功能。
该模式的关键点是:
- 抽象A具有多个具体子类
- 装饰器类依赖抽象A
- 装饰器类实现抽象A
- 装饰器类存在不同子类
最终的效果就是,(装饰器实现类)对(原抽象的子类)进行某些方法的功能加强。
把前文《Java设计模式(9)----------装饰器模式》中的UML图拿过来,结构是这样的
小结
其实从三者的UML图可以看出三种模式都是2个三角结构,但是位置不同。
- 适配器模式三角结构的位置是一上一下(因为适配器依赖抽象)。
- 桥接模式三角结构的位置是并列的(主体类直接依赖抽象)。
- 装饰器模式三角结构的位置也是并列的,但是多出了一条线(主体类不仅依赖抽象,而且实现该抽象接口)
外观模式
1、介绍
外观模式是一种结构型设计模式。在外观模式中,外观类中提供一个或者一组对外的接口。客户端对于复杂系统的访问是通过外观类的接口进行的,从而解除了客户端与复杂系统之间的耦合,隐藏了复杂系统内部的逻辑。
该设计模式主要解决客户端与复杂系统内部子系统的沟通成本高的问题,通过外观类,简化了沟通流程,降低沟通成本。
主要的应用场景有:1、医院的接待员(外观类)。通过接待员,可以实现挂号、缴费和取药等一系列的动作,化繁为简。2、电脑启动。通过按一下启动键就可以启动电脑,而不需要我们挨个的给硬盘、cpu和内存上电。其应用场景,抽象来说,就是存在一个总管或者接待员的角色,帮助我们更高效的进行某种行为或者操作。
2、案例
2.1、背景
智能家居是一个比较火的领域。我们的屋子里边会配置电器(复杂系统),每种电器,比如日光灯,电视,空调,冰箱(子系统)等都具有自己的开关。借助智能家居的思想,我们可以在手机上安装一个app,通过app(外观类)控制如上这些电器的开启和关闭。
2.2、实现
定义复杂系统和子系统
/**
* 电器抽象
*/
public interface appliance
{
void powerOn();
void powerOff();
}
public class AirConditioning implements appliance
{
@Override public void powerOn()
{
System.out.println("打开空调。");
}
@Override public void powerOff()
{
System.out.println("关闭空调。");
}
}
public class Lamp implements appliance
{
@Override public void powerOn()
{
System.out.println("打开台灯。");
}
@Override public void powerOff()
{
System.out.println("关闭台灯。");
}
}
public class Refrigerator implements appliance
{
@Override public void powerOn()
{
System.out.println("打开冰箱。");
}
@Override public void powerOff()
{
System.out.println("关闭冰箱。");
}
}
public class TV implements appliance
{
@Override public void powerOn()
{
System.out.println("打开电视。");
}
@Override public void powerOff()
{
System.out.println("关闭电视。");
}
}
定义外观类
外观类中形成统一的界面,包含子系统的所有对外功能,用于呈现给客户端使用。
public class App
{
private Lamp lamp;
private Refrigerator refrigerator;
private TV tv;
private AirConditioning airConditioning;
public App()
{
this.lamp = new Lamp();
this.refrigerator = new Refrigerator();
this.tv = new TV();
this.airConditioning = new AirConditioning();
}
public void powerOnAppliance()
{
this.airConditioning.powerOn();
this.lamp.powerOn();
this.refrigerator.powerOn();
this.tv.powerOn();
System.out.println("");
}
public void powerOffAppliance()
{
this.airConditioning.powerOff();
this.lamp.powerOff();
this.refrigerator.powerOff();
this.tv.powerOff();
System.out.println("");
}
public void powerOnLamp()
{
this.lamp.powerOn();
}
public void powerOffLamp()
{
this.lamp.powerOff();
}
}
验证程序
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
App app = new App();
app.powerOnLamp();
app.powerOnAppliance();
app.powerOffLamp();
}
}
运行结果
打开台灯。
打开空调。
打开台灯。
打开冰箱。
打开电视。关闭台灯。
Process finished with exit code 0
3、总结
优点:将客户端与复杂系统解耦,简化了使用流程。
缺点:不引入抽象外观类的情况,违背了开闭原则。
享元模式
1、介绍
享元模式通过尽可能的复用已经存在的对象,从而尽量少的创建新对象,以节约系统的内存和提供系统性能。在享元模式中,对象的属性分为两种,内部状态和外部状态。内部状态指的是对象中不随外部变化的属性,属于对象本身的特征属性(比如ID)。外部状态指的是对象中可以随外部变化的属性,可以在外部设置对象的外部状态。
享元模式的思想就是缓存和复用,重点关注的是性能。所以当系统中存在大量的对象,内存开销较大或者对象的大部分的属性是外部状态情况下,可以考虑享元模式。抽象一下,可以通过缓存解决的问题,一般都可以应用享元模式。
2、案例
2.1、背景
维秘秀(维多利亚的秘密)是最高等级的秀场,全世界的超模也以能登上维秘秀舞台为傲。对于每一套造型(对象)来说,包含模特(对象的内部状态)和服装(对象的外部状态)。能使用的模特是有限的,但是要展示的服装却要远远超出这个数量,所以在展示每一套服装的时候,需要对模特进行复用。导演要展示一个造型,只需要从目前可用的模特人群(缓存)中喊一个过来,穿上对应的服装,即可走上T台。那么如果目前没有可用的模特怎么办呢?那就招个模特进来,实在不行导演自己上咯。
2.2、实现
1)秀接口
public interface Show
{
void show();
}
2) 造型类
public class Model implements Show
{
private String name;
private String style;
public Model(String name)
{
this.name = name;
}
public void setStyle(String style)
{
this.style = style;
}
@Override public void show()
{
System.out.println(name+"展示"+style);
}
}
3)造型工厂,带有缓存池
import java.util.HashMap;
import java.util.Map;
public class ModelFactory
{
private static Map<String, Show> models = new HashMap<String, Show>();
public static Show getShow(String name)
{
Model model = (Model)models.get(name);
if (model == null)
{
model = new Model(name);
models.put(name,model);
}
return model;
}
}
4)验证程序
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Model model = (Model)ModelFactory.getShow("米兰达·可儿");
model.setStyle("迷幻Bono系列");
model.show();
model = (Model)ModelFactory.getShow("刘雯");
model.setStyle("冰与火系列");
model.show();
model = (Model)ModelFactory.getShow("米兰达·可儿");
model.setStyle("天使肖像系列");
model.show();
}
}
5)运行结果
米兰达·可儿展示迷幻Bono系列
刘雯展示冰与火系列
米兰达·可儿展示天使肖像系列Process finished with exit code 0
3、总结
优点:对象复用,降低内存消耗。
缺点:无法做到线程 安全,如果一个线程正在复用,修改其外部状态,而另外一个线程正在进行使用,就会造成问题。
代理模式
介绍
代理模式中,在客户端与对象之间增加了一个代理层。客户端在进行访问时候,不是直接访问对象,而是访问代理。代理模式是一种结构型的设计模式。通过代理模式,可以解决直接访问对象带来的一些问题,并且可以进行访问控制。
代理模式的实现中,代理类中依赖实体类,但两者共同实现相同的接口。代理类中对应接口中,会调用实体类的对应接口。听上去与装饰器的实现一样,是的,两者的实现基本上是相同的,只是使用意义上的侧重点不同。两者的区别,会在文末的总结中进行分析。
代理模式的应用场景比较广泛,跟“代理”两个字沾边的,一般都会涉及代理模式。比如火车票代售站,对于乘客来说,他不需要去真正的火车站,而通过代售点,就可以实现买票取票的功能。比如VPN,由于众所周知的原因,我们不能访问youtube,那么我们可以设置一个VPN,通过VPN进行访问。再比如说windows的快捷方式,也是一种代理。还有就是Spring的AOP。
代理模式从类型上来说分为两种,静态代理和动态代理。所谓静态代理,就是在编译期间就确定的代理关系,一般是一对一的。而动态代理,是一对多的代理关系,是在运行时态才能确定的代理关系。动态代理的原理比较复杂,本篇文章只简单介绍动态代理的使用,在下一篇文章中,会从原理和代码的层面,对动态代理做专门的解读。
案例
静态代理
背景
以购买火车票为例。乘客(客户端)在购买火车票的时候,可以在代售点(代理类)进行购买(功能),而不需要花费很长的时间跑去火车站(真实类)进行购买(功能)。
实现
定义抽象接口
public interface shop {
void buy();
void take();
}
定义真实火车站
public class Station implements shop
{
@Override
public void buy() {
System.out.println("购买火车票");
}
@Override
public void take() {
System.out.println("领取火车票");
}
}
定义代售点
public class ProxyStation implements shop
{
private Station station = new Station();
@Override
public void buy() {
beforeBuy();
station.buy();
afterBuy();
}
private void beforeBuy() {
System.out.println("购买之前操作");
}
private void afterBuy() {
System.out.println("购买之后的操作");
}
@Override
public void take() {
beforeTake();
station.take();
afterTake();
}
private void afterTake() {
System.out.println("取票之前的操作");
}
private void beforeTake() {
System.out.println("取票之后的操作");
}
}
验证程序
public class Test {
public static void main(String args[])
{
ProxyStation proxyStation = new ProxyStation();
proxyStation.buy();
proxyStation.take();
}
}
运行结果
购买之前操作
购买火车票
购买之后的操作
取票之后的操作
领取火车票
取票之前的操作Process finished with exit code 0
动态代理
动态代理的场景是一对多的关系,即是一个代理类,多个被代理的类。
在动态代理中,不再关注向客户端屏蔽原始对象(客户端可以看到原始对象),而重点是对于一系列的原始实现类,能够对抽象中的方法进行统一的横向扩展(方法执行前/后的操作),而不需要为每一个原始实现类都创建一个代理。
动态代理的原理,会在下篇文章中进行分析。
案例
以汽车(抽象)行驶为例子,我们要统计多种车型(原始实现类)通过某段距离的用时。在汽车行驶前,需要进行开始计时的操作(功能扩展)。汽车行驶后,要进行结束计时的操作(功能扩展)。
实现
抽象接口
public interface Moveable {
void move() throws Exception;
void move_back() throws Exception;
}
定义实现类
import java.util.Random;
public class Car implements Moveable {
public void move() throws Exception {
Thread.sleep(new Random().nextInt(1000));
System.out.println("轿车行驶中…");
}
public void move_back() throws Exception {
Thread.sleep(new Random().nextInt(1000));
System.out.println("轿车向后行驶中…");
}
}
import java.util.Random;
public class Truck implements Moveable {
public void move() throws Exception {
Thread.sleep(new Random().nextInt(1000));
System.out.println("卡车行驶中…");
}
public void move_back() throws Exception {
Thread.sleep(new Random().nextInt(1000));
System.out.println("卡车向后行驶中…");
}
}
定义动态代理工具类
动态代理工具类需要实现InvocationHandler
接口,实现invoke
方法,通过反射实现对抽象接口方法的调用method.invoke(target, args);
。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler {
private Object target;
public TimeHandler(Object target) {
super();
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶…");
method.invoke(target, args);
long stopTime = System.currentTimeMillis();
System.out.println("汽车结束行驶…汽车行驶时间:" + (stopTime - startTime) + "毫秒!");
return null;
}
}
验证程序
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception{
Car car = new Car();
Class<?> cls = car.getClass();
InvocationHandler h = new TimeHandler(car);
Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
m.move();
System.out.println("");
m.move_back();
System.out.println("");System.out.println("");
Truck truck = new Truck();
cls = truck.getClass();
h = new TimeHandler(truck);
m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
m.move();
System.out.println("");
m.move_back();
System.out.println("");System.out.println("");
}
}
运行结果
汽车开始行驶…
轿车行驶中…
汽车结束行驶…汽车行驶时间:200毫秒!汽车开始行驶…
轿车向后行驶中…
汽车结束行驶…汽车行驶时间:681毫秒!汽车开始行驶…
卡车行驶中…
汽车结束行驶…汽车行驶时间:492毫秒!汽车开始行驶…
卡车向后行驶中…
汽车结束行驶…汽车行驶时间:338毫秒!Process finished with exit code 0
总结
代理模式优缺点:
- 静态代理模式:优点是具有较好的扩展性,屏蔽原始实现类;缺点是增加了中间层可能会导致性能下降。
- 动态代理模式:优点是能对多个原始实现类进行统一的功能扩展;缺点是增加了中间层可能会导致性能下降。
代理模式与装饰器模式的区别:
- 代理模式强调对于访问的控制,在调用实体类的方法之前或者之后,会添加一些操作,是扩展的广度。装饰器模式强调对于方法的加强,扩展的深度。打个比方,张无忌学习乾坤大挪移。装饰器模式,则增加其深度,从第3层境地学习到第4层境地,注重功能的加强。代理模式则是让其在学习乾坤大挪移之前,先学习一下九阳神功,两种武功能够有所联系。从外界看来,都是武功的增强,但是一个是深度,一个是广度。
- 代理模式是代理,装饰器模式是装饰,两者在对原始对象的可见性上是不同的。代理模式中,在客户看来,无法感知到原始对象,只能接触代理对象。装饰器模式中,客户看来,是能够感知到原始对象的。
动态代理原理源码分析
上篇文章《Java设计模式(13)----------代理模式》中,介绍了两种代理模式(静态代理和动态代理)的应用场景和实际应用案例。本篇文章中,对动态代理的原理做进行深入的分析。
关于动态代理,初看可能会比较费解,大概有如下几个疑问:
- 代理是怎么形成的,代理类在哪里?
TimeHandler
类是做什么用的,在哪里被调用了?- 客户端调用的时候,写的是调用
m.move();
,程序是如何执行到了TimeHandler
对象的invoke
方法中了呢?
这篇文章,主要针对如上几个疑问进行展开。
首先声明一点,案例中的TimeHandler
并不是代理类,而是代理依赖的一个类而已。真正的代理类,是JDK直接生成的字节码并进行加载的,所以对用户是不可见的。
生成代理类的代码是这个:Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
这里向newProxyInstance
传递了三个参数,分别是原始类的加载器,原始类实现的接口(Moveable
),TimeHandler
类的对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
newProxyInstance
的代码实现比较清晰,通过getProxyConstructor
方法创建了代理类,并返回了该类的构造方法。之后使用反射,生成代理类的对象并返回。
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces)
{
// optimization for single interface
if (interfaces.length == 1) {
Class<?> intf = interfaces[0];
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
return proxyCache.sub(intf)puteIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
// interfaces cloned
final Class<?>[] intfsArray = interfaces.clone();
if (caller != null) {
checkProxyAccess(caller, loader, intfsArray);
}
final List<Class<?>> intfs = Arrays.asList(intfsArray);
return proxyCache.sub(intfs)puteIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
}
在getProxyConstructor
函数中,可以看到if-else分支,这里是对单接口的情况做了代码优化。我们主要关注其代理类的生成部分,就是new ProxyBuilder(ld, clv.key()).build()
。
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build() 这种写法是lambda表达式的语法,非常精简。
Constructor<?> build() {
Class<?> proxyClass = defineProxyClass(module, interfaces);
final Constructor<?> cons;
try {
cons = proxyClass.getConstructor(constructorParams);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
return cons;
}
在build方法中,从语意上可以看到代理类proxyClass
在defineProxyClass
方法中生成。之后通过反射并根据构造函数的参数,获取到代理类的构造方法并返回。
这里的参数为private static final Class<?>[] constructorParams = { InvocationHandler.class };
。所以看到这里,应该明白了TimeHandler
的作用了,就是作为代理类的构造方法的一个参数,也就是代理类依赖的对象。到这里,也就找到了文章开始处提出的问题二的答案。这里可以猜测一下,代理类中的对应接口的方法,应该是调用的TimeHandler类的invoke方法,通过控制invoke的参数,来调用不同的方法。
在defineProxyClass
方法中,最关键的代码如下:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
0, proxyClassFile.length,
loader, null);
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
return pc;
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
这里先生成字节码,之后将其加载到内存中。
static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
int i = name.lastIndexOf('.');
Path path;
if (i > 0) {
Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
Files.createDirectories(dir);
path = dir.resolve(name.substring(i+1, name.length()) + ".class");
} else {
path = Paths.get(name + ".class");
}
Files.write(path, classFile);
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
在generateProxyClass
方法中,先生成了ProxyGenerator
对象,然后调用对象的generateClassFile
生成字节码。可以看到其中有个saveGeneratedFiles
的标志位,表示是否需要保存生成的字节码文件。在generateClassFile
方法中,创建了ProxyMethod,字段表和方法表集合,并将内容按照字节码的规范写入到流中。至此代理类就生成了,文章开始处的问题一就回答完毕了
此处代码中可以根据语意看到会使用
InvocationHandler
的方法,目前对java的class文件的格式还不够了解,有机会研究一下。
刚刚说到代码中是存在打印生成的代理类的逻辑的,就是saveGeneratedFiles
标志位,在该变量定义处可以看到:
private static final boolean saveGeneratedFiles =
java.security.AccessController.doPrivileged(
new GetBooleanAction(
"jdk.proxy.ProxyGenerator.saveGeneratedFiles")).booleanValue();
表示该变量是由jdk.proxy.ProxyGenerator.saveGeneratedFiles
控制的。所以我们在代码中只需要指定这个值为true就可以了,代码如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception{
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
Car car = new Car();
Class<?> cls = car.getClass();
InvocationHandler h = new TimeHandler(car);
Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
m.move();
System.out.println("");
m.move_back();
System.out.println("");System.out.println("");
}
}
运行之后,就可以在工程目录下找到对应的代理类的class文件啦
使用idea打开,可以看到对应的java代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Moveable {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void move() throws Exception {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (Exception | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void move_back() throws Exception {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (Exception | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("Moveable").getMethod("move");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("Moveable").getMethod("move_back");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
代理类解读:
- 代理类的构造方法,接收一个
InvocationHandler
对象,调用父类Proxy的初始化方法,使用该对象为依赖h
的对象赋值。 - 以move方法为例,我们可以看到,代理类实现了
Moveable
接口,在move方法的实现中,调用了InvocationHandler
对象的invoke方法,并且其中第二个参数为m3对象。而m3对象是静态块中通过反射获取到的Moveable
接口的move
方法。所以,此处就回答的文章开始处的问题三。
到这里,动态代理的原理就非常清晰了,代理类是jdk直接以字节码的形式生成出来的,继承Proxy类,实现客户端定义的接口。因为Proxy依赖InvocationHandler
实现类,所以代理类也同样依赖。对于接口中定义的函数move,代理类中也会实现,其逻辑是调用InvocationHandler
类中的invoke方法,并将方法move方法作为invoke的一个参数。在invoke方法中,可以进行功能扩展,进而执行invoke参数中的方法,完成对原始对象的move方法的调用。不得不佩服java源码的作者们的深厚功力,能够设计出如此高扩展的结构,自己需要学习和实践的还有很多。
观察者模式
介绍
观察者模式主要解决的问题是一对多的依赖情况下,被依赖对象的消息通知问题,属于行为型设计模式。在程序设计的时候,有可能多个对象依赖同一个对象。当被依赖的这个对象有状态变更或者需要向依赖的类进行消息通知的时候,可以使用观察者模式。
实现方法:在观察者模式中,一般存在观察者角色,抽象观察者角色,被观察者角色(有的场景还会存在抽象被观察者的角色)。
- 观察者需要依赖被观察者,根据被观察者的某些状态改变而变更自己的状态。
- 被观察者对象中,会保存所有依赖该对象的观察者对象,当自身出现状态变更的时候,遍历观察者对象进行通知。
应用场景:观察者模式的使用场景就是一对多的对象依赖关系的场景,在实际生活中存在大量的观察者模式的例子(谁叫我们国家人多呢~)
- 电视转播是一种观察者模式,所有家庭中的电视都是观察者类对象,依赖电视台的记者,所以记者是被观察者类对象,当发生某种新闻或者事件,则向所有的电视客户端进行推送。
- 买房也是一种观察者模式的应用。开发商的置业顾问就是被观察者类的对象,而购房者都依赖置业顾问,是观察者类对象。当出现楼盘消息时候,置业顾问会遍历自己的用户列表,将消息通知到所有客户。
案例
背景
以上文提到的购房的场景为例。某一楼盘准备开盘,期间,会有很多的意向购房者(观察者类)前去看房,到了销售大厅之后,一般会有一个置业顾问(被观察者类)进行接待,介绍楼盘情况。最后,购房者会向置业顾问交换联系方式(形成依赖)。当后续楼盘有什么价格公布之类的消息的时候,置业顾问会向所有自己接待的客户发送消息(遍历观察者类,进行消息通知)。
实现
抽象观察者
public interface Customer {
void action();
void inform();
}
观察者
public class HomeBuyer implements Customer
{
private Salesperson salesperson;
private String name;
public HomeBuyer(String name, Salesperson salesperson) {
this.name = name;
this.salesperson = salesperson;
this.salesperson.addCustomer(this);
}
@Override
public void action() {
System.out.println(this.name + ":让我思考一下要不要买房。");
}
@Override
public void inform() {
System.out.println(this.name + ":收到信息:" + this.salesperson.getInfo());
action();
}
}
被观察者
import java.util.ArrayList;
import java.util.List;
public class Salesperson
{
private List<Customer> customers = new ArrayList<Customer>();
private String info = null;
public void addCustomer(Customer customer)
{
this.customers.add(customer);
}
public void setMessage(String info)
{
this.info = info;
sendMessage();
}
public String getInfo() {
return info;
}
private void sendMessage()
{
for (Customer customer : customers)
{
customer.inform();
}
}
}
验证程序
public class Test {
public static void main(String[] args){
Salesperson salesperson = new Salesperson();
new HomeBuyer("购房者A", salesperson);
new HomeBuyer("购房者B", salesperson);
new HomeBuyer("购房者C", salesperson);
salesperson.setMessage("楼盘价格出来了,2万一平");
}
}
运行结果
购房者A:收到信息:楼盘价格出来了,2万一平
购房者A:让我思考一下要不要买房。
购房者B:收到信息:楼盘价格出来了,2万一平
购房者B:让我思考一下要不要买房。
购房者C:收到信息:楼盘价格出来了,2万一平
购房者C:让我思考一下要不要买房。Process finished with exit code 0
总结
优点:形成触发机制。
缺点:注意避免循环依赖。观察者中的处理状态变更的方法最好做成异步的,避免因为某个一个观察者的处理错误导致整个系统无法进行。
命令模式
介绍
命令模式是一种行为型设计模式。在命令模式中,所有的请求都会被包装成为一个对象。
参考了一下其他关于命令模式的文章,其中有谈到说是可以用不同的请求对客户进行参数化。我对这句话的理解是,因为将请求封装成为对象,所以客户的所有操作,其实就是多个命令类的对象而已,即参数化了。
命令模式的最大的特点就是将请求的调用者与请求的最终执行者进行了解耦。调用者需要关心的仅仅是请求对象是否被执行了,对于请求对象是如何执行的,对什么进行操作的,统统不需要关心。
原理:命令模式中,一般有如下几个角色:
- command:命令的抽象接口,其中包含execute方法。根据业务需求,有的还会包含其他通用方法如undo等。
- concreteCommand:具体的命令实现类。每一种请求,都会映射一个具体的命令实现类。对于每个类,都会实现execute方法,并且依赖receiver,也就是接收者对象。execute方法中,一般就是调用接收者对象的对应方法,从而实现对请求的最终处理。
- receiver:请求的接收者,也是请求的最终的执行者,被命令实现类所依赖。
- invoker:请求的调用者。调用者会调用所有传入的命令对象的execute方法,开启命令的执行,但是不会与最终的执行者receive耦合,两者中间是通过命令实现类进行联系和沟通的。
- client:进行接收者对象和命令对象的创建,并建立两者之间的联系。
适用场景:涉及到“命令”、“操作”或者“控制”的场景,一般都是命令模式的适用场景。
- 餐厅点菜的过程,消费者(client)说要吃某几种菜(命令对象),赶快做好端上来。服务员(invoker)会记录所有点过的菜品(保存所有的命令对象),然后将订单给后厨说,按照单子做(调用所有命令对象的execute)。之后就会启动每一道菜品的制作流程。对于菜品如何烹制,与服务员是没有关系的,两者不耦合。
- 遥控器的运行过程也可以理解成是一种命令模式的应用。假设有一个智能家居的遥控器,在面板上,可以控制电灯的开关,空调的开关(各种命令对象)。遥控器就是invoker的角色,负责实际命令的调用。而最终命令的执行,则是各种电器(receiver)来进行的。
- 多线程的执行,也可以理解为一种命令模式的应用。 案例 背景
我们以智能家居的遥控器为例。手头上有一个智能遥控器(invoker),能够控制打开电灯,关闭电灯,打开电视,关闭电视等操作。
实现定义命令接口
public interface Command {
void execute();
}
定义receiver
public class Light {
public void open()
{
System.out.println("Light is open");
}
public void close()
{
System.out.println("Light is closed");
}
}
public class TV {
public void open()
{
System.out.println("TV is open");
}
public void close()
{
System.out.println("TV is closed");
}
}
定义具体命令
public class OpenLignt implements Command {
private Light light;
public OpenLignt(Light light) {
this.light = light;
}
@Override
public void execute() {
this.light.open();
}
}
public class CloseLight implements Command {
private Light light;
public CloseLight(Light light) {
this.light = light;
}
@Override
public void execute() {
this.light.close();
}
}
public class OpenTV implements Command {
private TV tv;
public OpenTV(TV tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.open();
}
}
public class CloseTV implements Command {
private TV tv;
public CloseTV(TV tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.close();
}
}
定义遥控器(invoker)
public class Controller {
private Command[] buttons;
private int num = 0;
public Controller() {
buttons = new Command[10];
}
public void addButton(Command command)
{
buttons[num++] = command;
}
public void pushButton(int number)
{
if (number > -1 && number < buttons.length)
{
buttons[number].execute();
}
}
}
验证程序
public class Test {
public static void main(String[] args){
Light light = new Light();
TV tv = new TV();
Controller controller = new Controller();
controller.addButton(new OpenLignt(light));
controller.addButton(new CloseLight(light));
controller.addButton(new OpenTV(tv));
controller.addButton(new CloseTV(tv));
controller.pushButton(0);
controller.pushButton(1);
controller.pushButton(2);
controller.pushButton(3);
}
}
程序输出
Light is open
Light is closed
TV is open
TV is closedProcess finished with exit code 0
扩展
命令模式中有一种扩展,叫做宏命令,能同时进行一组命令的执行。比如遥控器只存在两个按键,一个控制所有电器的开启,一个控制所有电器的关闭。那么我们不需要改动已有的代码,只要扩展一个组合命令类,其中包含多个命令即可。
public class MutilCommand implements Command {
private Command[] commands;
public MutilCommand(Command[] commands) {
thismands = commands;
}
@Override
public void execute() {
for (Command command : commands)
{
command.execute();
}
}
}
验证程序
public class Test {
public static void main(String[] args){
Light light = new Light();
TV tv = new TV();
Controller controller = new Controller();
controller.addButton(new OpenLignt(light));
controller.addButton(new CloseLight(light));
controller.addButton(new OpenTV(tv));
controller.addButton(new CloseTV(tv));
controller.addButton(new MutilCommand(new Command[]{new OpenLignt(light), new OpenTV(tv)}));
controller.addButton(new MutilCommand(new Command[]{new CloseLight(light), new CloseTV(tv)}));
controller.pushButton(0);
controller.pushButton(1);
controller.pushButton(2);
controller.pushButton(3);
System.out.println("");
controller.pushButton(4);
controller.pushButton(5);
}
}
运行结果
Light is open
Light is closed
TV is open
TV is closedLight is open
TV is open
Light is closed
TV is closedProcess finished with exit code 0
总结
命令模式的核心思想就是将命令或者请求封装成对象,分离请求调用者和请求最终执行者。
优点:将请求调用者和执行者解耦。
缺点:如果存在较多的命令或者请求,需要较多的命令类。
迭代器模式
介绍
迭代器模式的思想是将集合或者容器类中的对象遍历的职责分离出来,使用迭代器来进行负责。从而能够在顺序遍历集合或者容器的时候,无需关注其内部的细节和实现。
实现:迭代器模式中的关键实现包含一下几点:
- 集合或者容器类中包含内部类(迭代器类需要感知集合类的保存数据的形式和集合中元素数量),为迭代器的实现类
- 集合或者容器类中包含获取迭代器类对象的方法
- 迭代器抽象接口中包含
hasNext
和next
方法,对应检测集合中是否还存在下一个元素的方法和获取下一个元素的方法。
使用场景:需要遍历集合或者容器类,但是却无需了解其内部实现的情况。最广泛的应用就是Java中的iterator在Map,Set等集合中的应用,使得遍历集合非常方便。
案例
背景
迭代器是一种抽象的概念,所以迭代器模式在生活场景中基本没有对应场景,主要应用在软件设计中。所以我们直接举一个软件设计中的例子。我们需要保存若干个地点的名字,并且对这个集合进行遍历。
实现
迭代器接口
public interface Iterator
{
boolean hasNext();
Object next();
}
容器接口
public interface Container
{
Iterator getIterator();
}
容器实现类
public class PlaceContainer implements Container
{
private String[] places;
public PlaceContainer(String[] places)
{
this.places = places;
}
@Override
public Iterator getIterator()
{
return new PlaceIterator();
}
private class PlaceIterator implements Iterator
{
private int index;
@Override
public boolean hasNext()
{
return index < places.length;
}
@Override
public Object next()
{
return places[index++];
}
}
}
验证程序
public class Demo
{
public static void main(String[] args)
{
String[] places = new String[]{"beijing", "tianjin", "xian", "shanghai", "guangzhou"};
PlaceContainer placeContainer = new PlaceContainer(places);
for (Iterator iterator = placeContainer.getIterator();iterator.hasNext();)
{
System.out.println(iterator.next());
}
}
}
输出
beijing
tianjin
xian
shanghai
guangzhouProcess finished with exit code 0
扩展
java本身有提供Iterable
接口,其中包含iterator()
方法,可以用于为用户自定义的集合装备迭代器,非常方便。并且在此基础上,可以实现自定义集合类对于foreach关键字的支持。有兴趣的朋友可以看一下之前写过的一片博文《Java语言基础----------foreach原理》详细分析了foreach的实现原理。对于java本身的Iterable感兴趣的朋友,可以看一下java中各种Map或者Set的源码。
总结
迭代器模式用于顺序遍历集合类,却不暴露集合内部逻辑,分离了数据遍历职责。
策略模式
介绍
策略模式的思想是提取算法或者行为,对算法或者策略进行封装成类,并装配到一个环境类(context)上,使得环境类可以使用不同的算法或者策略来解决不同的问题。策略模式是一种行为型设计模式。
这种设计模式解决的是硬编码场景中的算法扩展问题。针对某一个场景,可能存在多个算法来进行处理。这多个算法,可能是写在一个工具类的不同的方法中,也有可能是写在一个方法中根据不同的条件进行if-else的选择。但是这样的处理方式的扩展性非常差劲,如果要增加一种算法,就需要修改工具类。要增加代码的扩展性,就需要为每个算法新建一个类,其中放置算法逻辑。这样当新增算法的时候,只需要增加对应的算法类即可,这就是策略模式的主要内容。
实现:策略模式中一般存在如下几个角色:
- 抽象策略接口:定义策略类的通用接口,是策略或者算法的调用入口。
- 具体策略类:策略实现类,根据业务场景包含某一种算法的真实执行逻辑。具体策略类用于装配环境类,从而让环境类能够有能力进行不同的处理逻辑。
- 环境类(context):依赖抽象策略接口,使用具体策略类装备自己。客户端根据具体场景,选择对应的具体策略类来装配环境类,为环境类赋能。环境类只负责处理具体场景下的问题。具体策略类的选择是客户端进行的,环境类不提供该能力。
使用场景:系统中存在多种可供选择的策略或者算法,针对各种不同的场景,需要提取出一种算法或者策略进行处理的时候,可以选择策略模式。
- 比如某些银行卡会推出普通会员卡,银卡,金卡和钻石卡。每种银行卡在购物时候享受的优惠是不同的,在计算优惠的时候,就存在了不同的策略和算法,比如会员卡9折,银卡8折,金卡7折和钻石卡6折,这样就存在了4中算法。当购物的时候,需要顾客的会员卡等级,选择出对应的策略来进行总价计算。
- 比如出去玩,可以步行,骑自行车,开车,打车。那么就需要从这么多的策略中选出一种来。比如为了锻炼身体,选择了步行。但是某人提出要赶时间,那就改为用打车的策略来装配决策类,改为打车的方式出行。 案例 背景
以假期出行为例,总是存在多种出行策略的选择。去浪漫的土耳其,去东京和巴黎,还特别喜欢迈阿密,有黑人的洛杉矶。假期有限,只能选择其中的一种。那么就跟小伙伴开始商量(context类),小伙伴A说土耳其比较便宜,毕竟经费有限。OK,那就决定去土耳其了。然后小伙伴B说,他家里有亲戚在东京,包吃包住。OK,那就取东京把。最后小伙伴C说了,他想去迈阿密,让大家跟他一起,所有经费他包了。OK,那就去迈阿密把。
实现抽象策略接口
public interface Strategy
{
void go();
}
具体策略类
public class TurkeyVocation implements Strategy
{
@Override
public void go()
{
System.out.println("去土耳其。");
}
}
public class TokyoVocation implements Strategy
{
@Override
public void go()
{
System.out.println("去东京。");
}
}
public class MiamiVocation implements Strategy
{
@Override
public void go()
{
System.out.println("去迈阿密拉。");
}
}
环境类
public class Context
{
private Strategy strategy;
public Context(Strategy strategy)
{
this.strategy = strategy;
}
public void setStrategy(Strategy strategy)
{
this.strategy = strategy;
}
public void action()
{
this.strategy.go();
}
}
验证程序
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
System.out.println("小伙伴A:土耳其比较便宜,毕竟经费有限");
Strategy strategy = new TurkeyVocation();
Context context = new Context(strategy);
context.action();
System.out.println("小伙伴B:家里有亲戚在东京,包吃包住");
strategy = new TokyoVocation();
context.setStrategy(strategy);
context.action();
System.out.println("小伙伴C:所有经费我包了");
strategy = new MiamiVocation();
context.setStrategy(strategy);
context.action();
}
}
运行结果
小伙伴A:土耳其比较便宜,毕竟经费有限
去土耳其。
小伙伴B:家里有亲戚在东京,包吃包住
去东京。
小伙伴C:所有经费我包了
去迈阿密拉。Process finished with exit code 0
总结
核心思想就是将算法或者策略封装成类。
优点:各种算法可以自由切换,具有非常好的扩展性。
缺点:每一种策略都需要封装成一个类,可能会存在较多的策略类。
模板方法模式
介绍
模版方法模式的思想是在抽象类中定义程序的主体框架,在子类中实现具体的细节,是一种行为型设计模式。
实现:模版方法模式中存在两个角色,抽象类和实现类。在抽象类中,定义了程序的主体结构或者流程,该方法可以设置成final的,这样子类中就无法重写。而对于其他的细节的方法定义,则延迟到实现类中。
使用场景:
- 比如做饭,都需要开火,架锅,放入食材这样一套的主体流程,这个可以放在抽象类中。但是开多大的火,使用哪种锅,放入什么食材,则放在子类中,因为每种菜品的的制作方法都不一样。
- 比如吃饭。都要先洗手,拿餐具,吃饭。主体流程都是统一的。但是用什么东西洗手,使用什么餐具吃饭,吃什么饭,都是存在多种情况的。
案例
背景
以每天的生活工作为例。主体框架就是,吃早饭,上班,吃午饭,上班,吃晚饭。具体实现的细节包含三餐吃什么,上班做什么。
实现
抽象类
public abstract class Life
{
abstract void makeBreakfast();
abstract void makeLunch();
abstract void makeDinner();
abstract void MorningWork();
abstract void AfternoonWork();
public final void live()
{
makeBreakfast();
MorningWork();
makeLunch();
AfternoonWork();
makeDinner();
}
}
子类
public class Chairman extends Life
{
@Override
void makeBreakfast()
{
System.out.println("早餐:吃西餐");
}
@Override
void makeLunch()
{
System.out.println("午餐:吃日本料理");
}
@Override
void makeDinner()
{
System.out.println("晚餐:吃满汉全席");
}
@Override
void MorningWork()
{
System.out.println("上午工作:处理公司内部事务");
}
@Override
void AfternoonWork()
{
System.out.println("下午工作:处理公司外部公关");
}
}
public class Programmer extends Life
{
@Override
void makeBreakfast()
{
System.out.println("早餐:肯德基");
}
@Override
void makeLunch()
{
System.out.println("午餐:麦当劳");
}
@Override
void makeDinner()
{
System.out.println("晚餐:德克士");
}
@Override
void MorningWork()
{
System.out.println("上午工作:写程序");
}
@Override
void AfternoonWork()
{
System.out.println("下午工作:调试程序");
}
}
验证程序
public class Demo
{
public static void main(String[] args)
{
Programmer programmer = new Programmer();
programmer.live();
System.out.println("");
Chairman chairman = new Chairman();
chairman.live();
}
}
运行结果
早餐:肯德基
上午工作:写程序
午餐:麦当劳
下午工作:调试程序
晚餐:德克士早餐:吃西餐
上午工作:处理公司内部事务
午餐:吃日本料理
下午工作:处理公司外部公关
晚餐:吃满汉全席Process finished with exit code 0
总结
优点:将不变的主体部分提取到抽象类中,作为模版方法,便于维护
中介者模式
介绍
中介者模式是将多个对象之间通信的网状结构转化为星状结构,从而避免多个对象之间的相互耦合,是一种行为型的设计模式。中介者模式的思路就是加入一个中介者对象,所有对象与对象之间的通信,均通过中介者来进行,所以每个对象不再依赖其他的对象。
实现:中介者模式中一般存在多个同事类或者对象,中介者类。
- 同事类或者对象:通常一个对象的状态变化或者行为需要对其他同事产生影响。每个同事对象依赖中介者,通过中介者传达影响。
- 中介者:被同事类依赖,负责进行同事之间消息的通知。
适用场景:一般存在网状结构的场景,都可以通过中介者模式来进行优化。比如课堂上每个同学的课堂发言或者小组讨论,比如微信的群组的实现等,都是网状的模型。
案例
背景
比如模拟一个班级上同学之间的谈话。班上有三个女生A、B和C,有三个男生D、E和F。女生A想说一些话,只想让女生知道。男生D则想说一些话,让所有的人都可以听到。这是一个很明显的网状通信的问题。
实现
同事类
public class Student {
private String name;
private String sex;
public Student(String name, String sex) {
this.name = name;
this.sex = sex;
Mediator.add(this);
}
public String getSex() {
return sex;
}
public String getName() {
return name;
}
public void sendMessageToAll(String message)
{
Mediator.sendMessageToAll(this, message);
}
public void sendMessageToSameSex(String message)
{
Mediator.sendMessageToSameSex(this, message);
}
public void receiveMessage(String message)
{
System.out.println(name + "收到了消息。" + message);
}
}
中介者
import java.util.ArrayList;
import java.util.List;
public class Mediator {
private static List<Student> maleStudents = new ArrayList<Student>();
private static List<Student> femaleStudents = new ArrayList<Student>();
public static void add(Student student)
{
if ("male".equals(student.getSex()))
{
maleStudents.add(student);
}
else
{
femaleStudents.add(student);
}
}
public static void sendMessageToAll(Student student, String message)
{
for (Student stu : maleStudents)
{
if (student.getName().equals(stu.getName()))
continue;
stu.receiveMessage(student.getName() + ":" + message);
}
for (Student stu : femaleStudents)
{
if (student.getName().equals(stu.getName()))
continue;
stu.receiveMessage(student.getName() + ":" + message);
}
}
public static void sendMessageToSameSex(Student student, String message)
{
List<Student> tempList = "male".equals(student.getSex()) ? maleStudents : femaleStudents;
for (Student stu : tempList)
{
if (student.getName().equals(stu.getName()))
continue;
stu.receiveMessage(student.getName() + ":" + message);
}
}
}
验证程序
public class Test {
public static void main(String[] args){
Student femaleA = new Student("小丽","female");
Student femaleB = new Student("小红","female");
Student femaleC = new Student("小美","female");
Student femaleD = new Student("小刚","male");
Student femaleE = new Student("小明","male");
Student femaleF = new Student("小达","male");
femaleA.sendMessageToAll("大家好,我叫小丽");
femaleA.sendMessageToSameSex("舍友们,今天咱们去哪里吃饭?");
femaleD.sendMessageToSameSex("走,去躺厕所。");
}
}
输出结果
小刚收到了消息。小丽:大家好,我叫小丽
小明收到了消息。小丽:大家好,我叫小丽
小达收到了消息。小丽:大家好,我叫小丽
小红收到了消息。小丽:大家好,我叫小丽
小美收到了消息。小丽:大家好,我叫小丽
小红收到了消息。小丽:舍友们,今天咱们去哪里吃饭?
小美收到了消息。小丽:舍友们,今天咱们去哪里吃饭?
小明收到了消息。小刚:走,去躺厕所。
小达收到了消息。小刚:走,去躺厕所。
总结
中介者模式通过加入中介者角色,使得所有对象之间的消息通知都通过中介者进行,从而将网状模型转变为星状模型。
优点:解除同事对象之间的耦合关系
缺点:中介者可能会比较庞大和复杂。
作者:道可
链接:http://www.imooc/article/25971
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作
版权声明:本文标题:Java设计模式吐血整理 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1729047620h1311210.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论