23种设计模式
1.单例模式
概念:
单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
关键点:
1)一个类只有一个实例
2)它必须自行创建这个实例
3)它必须自行向整个系统提供这个实例
什么时候使用单例模式:
当一个项目中 要求某类只有一个对象时!
应用场景:
1、项目中只想多次调用该对象的方法时,为了提高效率
2、如果创建多个对象,会导致逻辑错误时
饿汉模式
/**
* 单例模式(饿汉式)
* 在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
*/
public class EagerSingleton {
//1.静态私有成员,已初始化,只能在类的内部访问
private static EagerSingleton instance=new EagerSingleton();
//2.私有的构造器
private EagerSingleton(){}
//3.静态,不用同步(类加载时已初始化,不会有多线程的问题)
public static EagerSingleton getInstance(){
return instance;
}
}
/**
* 测试类
*/
class TestEagerSingleton{
public static void main(String[] args) {
EagerSingleton instance1 = EagerSingleton.getInstance();
EagerSingleton instance2 = EagerSingleton.getInstance();
System.out.println(instance1+" "+instance2);
if (instance1==instance2){
System.out.println("instance1 is equals instance2");
}
}
}
关键点:
1)私有构造函数
2)静态私有成员–在类加载时已初始化
3)公开访问点getInstance–不需要同步,因为在类加载时已经初始化完毕,也不需要判断null,直接返回
懒汉模式
懒汉模式(类加载时不初始化)
/**
* 单例模式(懒汉式)
* 在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢
*/
public class LazySingleton {
//1.私有的静态成员,只能在类的内部访问
private static LazySingleton singleton = null;
//2.private的构造器,不能在类的外部创建该类的对象
private LazySingleton() {
}
//3.公有的,静态的方法,不用创建对象即可访问
public static synchronized LazySingleton getSingleton() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
/**
* 测试类
*/
class TestLazySingleton{
public static void main(String[] args) {
LazySingleton singleton1 = LazySingleton.getSingleton();
LazySingleton singleton2 = LazySingleton.getSingleton();
if (singleton1==singleton2){
System.out.println("singleton1 is equals singleton2");
}
}
}
关键点:
1)构造函数定义为私有----不能在别的类中来获取该类的对象,只能在类自身中得到自己的对象
2)成员变量为static的,没有初始化----类加载快,但访问类的唯一实例慢,static保证在自身类中获取自身对象
3)公开访问点getSingleton: public和synchronized的-----public保证对外公开,同步保证多线程时的正确性(因为类变量不是在加载时初始化的)
区别
饿汉式上去就把对象私有化,new,并且设置为static,防止多线程产生的冲突。
懒汉式不是上去就把对象new好的,在返回方法的对象里创建了new的对象,存在多线程的问题。
饿汉式和懒汉式的差异:
1、创建对象的时机不同!
2、应用场合不同
线程安全:饿汉式在线程还没出现之前就已经实例化了,所以饿汉式一定是线程安全的。懒汉式加载是在使用时才会去new 实例的,那么你去new的时候是一个动态的过程,是放到方法中实现的
执行效率:饿汉式没有加任何的锁,因此执行效率比较高。懒汉式一般使用都会加同步锁,效率比饿汉式差。
内存使用:饿汉式在一开始类加载的时候就实例化,无论使用与否,都会实例化,所以会占据空间,浪费内存。懒汉式什么时候用就什么时候实例化,不浪费内存
Runtime类就使用了饿汉式单例设计模式
2.代理模式
定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
为什么要用代理模式?
中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类
静态代理
1.创建接口
/**
* 代理模式的接口
*/
public interface KindWoMan {
String say();
}
2.创建委托类
/**
* 真实类(委托类)
*/
public class Pan implements KindWoMan{
@Override
public String say() {
return "西门庆好帅";
}
}
3.创建代理类
/**
* 代理类
*/
public class Wang implements KindWoMan{
private KindWoMan kindWoMan;
/**
*
* @param kindWoMan 委托类的对象
*/
Wang(KindWoMan kindWoMan){
this.kindWoMan=kindWoMan;
};
@Override
public String say() {
String realContent = kindWoMan.say();
String str="我见过很多男的,"+realContent;//前置增强
new XiMen().rec(str);
return null;
}
}
4.创建目标类
/**
* 目标类
*/
public class XiMen {
public void rec(String str){
System.out.println(str);
}
}
5.测试类
/**
* 测试类
*/
public class App {
public static void main(String[] args) {
//委托类的对象
Pan pan=new Pan();
//创建代理类的对象
Wang wang=new Wang(pan);
//调用了代理类的say方法
wang.say();
}
}
静态代理总结:
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态的来创建。
1.创建接口
/**
* 代理模式的接口
*/
public interface KindWoMan {
String say();
}
2.创建委托类
/**
* 真实类(委托类)
*/
public class Pan implements KindWoMan {
@Override
public String say() {
return "西门庆好帅";
}
}
public class Liu implements KindWoMan{
@Override
public String say() {
return "西门庆好帅!!!!!";
}
}
3.创建目标类
/**
* 目标类
*/
public class XiMen {
public void rec(String str){
System.out.println(str);
}
}
4.测试类
public class Main {
public static void main(String[] args) {
//创建委托类的对象
Pan pan=new Pan();
//使用jdk自带的动态代理
KindWoMan proxyInstance = (KindWoMan) Proxy.newProxyInstance(Main.class.getClassLoader(), Pan.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("say")){
String say = pan.say();
return "hello,"+say;
}
return null;
}
});
//调用代理对象的say方法
String say = proxyInstance.say();
new XiMen().rec(say);
}
}
注意Proxy.newProxyInstance()方法接受三个参数:
ClassLoader loader
:*指定当前目标对象使用的类加载器,获取加载器的方法是固定的Class[] interfaces
:*指定目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler:
*指定动态处理器
,执行目标对象的方法时,会触发事件处理器的方法
动态代理总结 :虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾动态生成的代理类的继承关系中,它们有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。
没有接口的类,如何实现动态代理呢,这就需要CGLIB。CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
3.工厂方法模式
根据产品是具体产品还是具体工厂可分为简单工厂模式和工厂方法模式,根据工厂的抽象程度可分为工厂方法模式和抽象工厂模式。该模式用于封装和管理对象的创建,是一种创建型模式。
简单工厂模式
该模式对对象创建管理方式最为简单,因为其仅仅简单的对不同类对象的创建进行了一层薄薄的封装。该模式通过向工厂传递类型来指定要创建的对象
生产手机案例
Phone接口
public interface Phone {
void make();
}
HuaWeiPhone
public class HuaWeiPhone implements Phone{
public HuaWeiPhone(){
this.make();
}
@Override
public void make() {
System.out.println("制作华为手机");
}
}
XiaoMiPhone
public class XiaoMiPhone implements Phone {
public XiaoMiPhone()
{
this.make();
}
@Override
public void make() {
System.out.println("制作小米手机");
}
}
PhoneFactory
public class PhoneFactory {
public Phone makePhone(String phoneType){
if (phoneType.equalsIgnoreCase("XiaoMiPhone")){
return new XiaoMiPhone();
}
else if (phoneType.equalsIgnoreCase("HuaWeiPhone")){
return new HuaWeiPhone();
}
return null;
}
}
测试类
public class Demo {
public static void main(String[] args) {
PhoneFactory phoneFactory = new PhoneFactory();
Phone xiaoMiPhone = phoneFactory.makePhone("XiaoMiPhone");
HuaWeiPhone huaWeiPhone =(HuaWeiPhone) phoneFactory.makePhone("HuaWeiPhone");
}
}
工厂方法模式(Factory Method)
和简单工厂模式中工厂负责生产所有产品相比,工厂方法模式将生成具体产品的任务分发给具体的产品工厂
也就是定义一个抽象工厂,其定义了产品的生产接口,但不负责具体的产品,将生产任务交给不同的派生类工厂。这样不用通过指定类型来创建对象了。
生产手机案例
AbstractFactory接口
public interface AbstractFactory {
Phone makePhone();
}
phone接口
public interface Phone {
void make();
}
HuaWeiPhone
public class HuaWeiPhone implements Phone{
public HuaWeiPhone(){
this.make();
}
@Override
public void make() {
System.out.println("制作华为手机");
}
}
XiaoMiPhone
public class XiaoMiPhone implements Phone {
public XiaoMiPhone()
{
this.make();
}
@Override
public void make() {
System.out.println("制作小米手机");
}
}
HuaWeiFactory
public class HuaWeiFactory implements AbstractFactory{
@Override
public Phone makePhone() {
return new HuaWeiPhone();
}
}
XiaoMiFactory
public class XiaoMiFactory implements AbstractFactory{
@Override
public Phone makePhone() {
return new XiaoMiPhone();
}
}
测试类
public class Main {
public static void main(String[] args) {
AbstractFactory xiaoMiFactory = new XiaoMiFactory();
AbstractFactory huaWeiFactory = new HuaWeiFactory();
xiaoMiFactory.makePhone();
huaWeiFactory.makePhone();
}
}
4.抽象工厂模式(Abstract Factory)
上面两种模式不管工厂怎么拆分抽象,都只是针对一类产品Phone(AbstractProduct),如果要生成另一种产品PC,应该怎么表示呢?
最简单的方式是把2中介绍的工厂方法模式完全复制一份,不过这次生产的是PC。但同时也就意味着我们要完全复制和修改Phone生产管理的所有代码,并不利于扩展和维护。
抽象工厂模式通过在AbstarctFactory中增加创建产品的接口,并在具体子工厂中实现新加产品的创建,当然前提是子工厂支持生产该产品。否则继承的这个接口可以什么也不干。
从上面类图结构中可以清楚的看到如何在工厂方法模式中通过增加新产品接口来实现产品的增加的。
案例
PC接口
public interface PC {
void make();
}
HuaWeiPC
public class HuaWeiPC implements PC {
public HuaWeiPC(){
this.make();
}
@Override
public void make() {
System.out.println("制作华为电脑");
}
}
XiaoMiPC
public class XiaoMiPC implements PC {
public XiaoMiPC(){
this.make();
}
@Override
public void make() {
System.out.println("制作小米电脑");
}
}
phone接口
public interface Phone {
void make();
}
HuaWeiPhone
public class HuaWeiPhone implements Phone{
public HuaWeiPhone(){
this.make();
}
@Override
public void make() {
System.out.println("制作华为手机");
}
}
XiaoMiPhone
public class XiaoMiPhone implements Phone {
public XiaoMiPhone()
{
this.make();
}
@Override
public void make() {
System.out.println("制作小米手机");
}
}
AbstractFactory接口
public interface AbstractFactory {
Phone makePhone();
PC makePC();
}
HuaWeiFactory
public class HuaWeiFactory implements AbstractFactory{
@Override
public Phone makePhone() {
return new HuaWeiPhone();
}
@Override
public PC makePC() {
return new HuaWeiPC();
}
}
XiaoMiFactory
public class XiaoMiFactory implements AbstractFactory{
@Override
public Phone makePhone() {
return new XiaoMiPhone();
}
@Override
public PC makePC() {
return new XiaoMiPC();
}
}
测试类
public class Main {
public static void main(String[] args) {
AbstractFactory xiaoMiFactory = new XiaoMiFactory();
AbstractFactory huaWeiFactory = new HuaWeiFactory();
xiaoMiFactory.makePhone();
xiaoMiFactory.makePC();
huaWeiFactory.makePhone();
huaWeiFactory.makePC();
}
}
上面介绍的三种工厂模式有各自的应用场景,实际应用时能解决问题满足需求即可,可灵活变通,无所谓高级与低级。
此外无论哪种模式,由于可能封装了大量对象和工厂创建,新加产品需要修改已定义好的工厂相关的类,因此对于产品和工厂的扩展不太友好,利弊需要权衡一下。
5.适配器模式(adapter pattern)
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作。
适配器模式是一种对象结构型模式。
这里的接口不仅仅是java语言中的interface,更多是指一个类型所具有的方法特征集合,是一种逻辑上的抽象。
使用场景
客户端需要一个target(目标)接口,但是不能直接重用已经存在的adaptee(适配者)类,因为它的接口和target接口不一致,所以需要adapter(适配器)将adaptee转换为target接口。前提是target接口和已存在的适配者adaptee类所做的事情是相同或相似,只是接口不同且都不易修改。如果在设计之初,最好不要考虑这种设计模式。凡事都有例外,就是设计新系统的时候考虑使用第三方组件,因为我们就没必要为了迎合它修改自己的设计风格,可以尝试使用适配器模式。
这是一个适配器使用场景的例子:
Sun公司在1996年公开了Java语言的数据库连接工具JDBC,JDBC使得Java语言程序能够与数据库连接,并使用SQL语言来查询和操作数据。JDBC给出一个客户端通用的抽象接口,每一个具体数据库引擎(如SQL Server、Oracle、MySQL等)的JDBC驱动软件都是一个介于JDBC接口和数据库引擎接口之间的适配器软件。抽象的JDBC接口和各个数据库引擎API之间都需要相应的适配器软件,这就是为各个不同数据库引擎准备的驱动程序。
角色
目标角色(target):这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
适配者角色(adaptee):已有接口,但是和客户器期待的接口不兼容。
适配器角色(adapter):将已有接口转换成目标接口。
分类
适配器模式有分三类:
- 1、类适配器模式(class adapter pattern)
- 2、对象适配器模式(object adapter pattern)
- 3、缺省适配器模式(default adapter pattern),也叫默认适配器模式、接口适配器模式
类适配器模式(class adapter pattern)
类适配器模式在编译时实现target(目标)接口。这种适配器模式使用了多个实现了期待的接口或者已经存在的接口的多态接口。比较典型的就是:target接口被创建为一个纯粹的接口,如Java不支持多继承的语言。
类适配器模式(adapter pattern)结构图:
如上图,因为java没有类多继承,所以只能实现Target接口,而且Target只能是接口。Adapter实现了Target接口,继承了Adaptee类,Target.operation()实现为Adaptee.specificOperation()。
客户端调用类适配器:
这个图是Adapter适配器多继承的情况,可以看到客户端调用适配器Adapter的methodA()时候,实际上调用的是Adapter继承过来的method1()到methodN()。
案例
将电源输入220V(适配者)转换为5V输出(目标)
目标角色(PowerTarget接口)
public interface PowerTarget {
int output5V();
}
适配者角色(PowerAdaptee)
public class PowerAdaptee {
private int output=220;
public int output220V(){
System.out.println("电源输出电压:" + output);
return output;
}
}
适配器角色(PowerAdapter)
/**
* 适配器角色
* 电源适配器类实现了电源目标,继承了适配者
*/
public class PowerAdapter extends PowerAdaptee implements PowerTarget{
@Override
public int output5V() {
int output=output220V();
System.out.println("电源适配器开始工作,此时输出电压是:" + output);
output=output/44;
System.out.println("电源适配器工作完成,此时输出电压是:" + output);
return output;
}
}
类适配器模式测试类(ClassAdapterPatternTest)
public class ClassAdapterPatternTest {
public static void main(String[] args) {
PowerTarget target = new PowerAdapter();
target.output5V();
}
}
输出结果
电源输出电压:220
电源适配器开始工作,此时输出电压是:220
电源适配器工作完成,此时输出电压是:5
对象适配器模式(object adapter pattern)
对象适配器模式在运行时实现target(目标)接口。在这种适配器模式中,适配器包装了一个类实例。在这种情况下,适配器调用包装对象实例的方法。
对象适配器模式(object adapter pattern)结构图:
如上图,与类适配器模式不同的是,Adapter只实现了Target的接口,没有继承Adaptee,而是使用聚合的方式引用adaptee。
客户端调用对象适配器:
客户端调用对象适配器方法methodA()的时候,实际上调用的是创建对象传进来的适配者实例的方法methodB()。
案例
PowerTarget接口
和PowerAdaptee
和上面的一样
适配器角色PowerAdapter
/**
* 适配器角色
* 电源适配器类继承了适配者
*/
public class PowerAdapter implements PowerTarget {
private PowerAdaptee powerAdaptee;
public PowerAdapter(PowerAdaptee powerAdaptee){
super();
this.powerAdaptee=powerAdaptee;
}
@Override
public int output5V() {
int output=powerAdaptee.output220V();
System.out.println("电源适配器开始工作,此时输出电压是:" + output);
output=output/44;
System.out.println("电源适配器工作完成,此时输出电压是:" + output);
return output;
}
}
测试类
public class ClassAdapterPatternTest {
public static void main(String[] args) {
PowerTarget target = new PowerAdapter(new PowerAdaptee());
target.output5V();
}
}
类适配器模式和对象适配器模式的对比
优点
类适配器模式(class adapter pattern):
由于适配器adapter类是适配者adaptee类的子类,因此可以在适配器类中置换一些适配者的方法,即Override(重写),使得适配器的灵活性更强。
对象适配器模式(object adapter pattern):
一个对象适配器可以把多个不同的适配者adaptee适配到一个目标,也就是说,同一个适配器可以将适配者类和它的子类都适配到目标接口。
缺点
类适配器模式:
java8之前:接口没有default方法,就是没有实现了具体逻辑的方法,而且不支持类多继承,所以适配者类只能有一个。
java8之后:接口有了default方法,接口中的方法有了实现,因为接口是多继承的,所以适配者可以是多个带有default方法的接口,但是接口是不可以实例化的,实际上没有什么意义。有个解决方法就是,接口里都是default方法,实现接口的类什么也没做,就是提供一个可以实例化的类。这样的话,类适配器模式中适配者adapter类就可以适配多个适配者adaptee类了。
对象适配器模式:
类适配器模式的优点就是对象适配器模式的缺点,不能置换适配者类的方法。如果想修改适配者类的一个或多个方法,就只好先创建一个继承于适配者类的子类,把适配者类的方法置换掉,然后把适配者的子类当做真正的适配者进行适配,实现过程较为复杂。
缺省适配器模式(default adapter pattern)
当不需要全部实现接口提供的方法时,可以设计一个适配器抽象类实现接口,并为接口中的每个方法提供默认方法,抽象类的子类就可以有选择的覆盖父类的某些方法实现需求,它适用于一个接口不想使用所有的方法的情况。在java8后,接口中可以有default方法,就不需要这种缺省适配器模式了。接口中方法都设置为default,实现为空,这样同样可以达到缺省适配器模式同样的效果。
缺省适配器模式结构图:
适配器Adapter类实现Target接口,方法默认为空。
案例
目标角色SampleOperation接口
public interface SampleOperation {
public abstract void operation1();
public abstract void operation2();
public abstract void operation3();
public abstract void operation4();
public abstract void operation5();
}
适配器角色DefaultAdapter
/**
* 适配器角色
* 默认实现了所有操作
*/
public class DefaultAdapter implements SampleOperation{
@Override
public void operation1() {
}
@Override
public void operation2() {
}
@Override
public void operation3() {
}
@Override
public void operation4() {
}
@Override
public void operation5() {
}
}
Operator
/**
* 这个是测试缺省适配器模式需要用到的类
*/
public class Operator {
private SampleOperation sampleOperation;
public void addOperator(SampleOperation sampleOperation){
this.sampleOperation=sampleOperation;
}
public void operation1(){
sampleOperation.operation1();
}
public void operation2(){
sampleOperation.operation2();
}
public void operation3() {
sampleOperation.operation3();
}
public void operation4() {
sampleOperation.operation4();
}
public void operation5() {
sampleOperation.operation5();
}
}
测试类
public class DefaultAdapterTest {
public static void main(String[] args) {
// 1、原来要实现所有操作类的操作
Operator operator1 = new Operator();
operator1.addOperator(new SampleOperation() {
@Override
public void operation1() {
}
@Override
public void operation2() {
System.out.println("操作2");
}
@Override
public void operation3() {
}
@Override
public void operation4() {
}
@Override
public void operation5() {
}
});
operator1.operation2();
// 2、使用缺省适配器只需要实现需要用到的接口方法
Operator operator2 = new Operator();
operator2.addOperator(new DefaultAdapter(){
public void operation2(){
System.out.println("操作2");
}
});
operator2.operation2();
}
}
测试类需要执行操作2,operator1添加SampleOperation时要实现接口里所有方法,operator2添加SampleOperation时只需要通过DefaultAdapter适配器添加自己需要的操作即可。毫无疑问,测试结果是一样的。
优点
1、复用性:系统需要使用已经存在的类,功能符合系统要求,但这个类的接口不符合系统的需求,通过适配器模式解决不兼容的问题,使这些功能类得到复用。
2、扩展性:适配器使得系统多了一个方式扩展系统的功能
3、耦合性:一定程度上的解耦
缺点
过多地使用适配器,增加系统理解难度。
适配器模式,本质上是现有的不兼容的接口转换为需要的接口。
类适配器模式,以继承现有类的方式转换。
对象适配器模式,以聚合对象实例的方式转换。
接口适配器模式,以实现接口的方式转换。
适配器模式是在现有的类和系统都不易修改的情况下使用,在系统设计之初慎用适配器模式。
6.观察者模式
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
在观察者模式中有如下角色:
Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
观察者模式结构图
其中,Subject类是主题,它把所有对观察者对象的引用文件存在了一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供了一个接口,可以增加和删除观察者对象;Observer类是抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己;ConcreteSubject类是具体主题,将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知;ConcreteObserver是具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协同。
案例
观察者模式这种发布-订阅的形式我们可以拿微信公众号来举例,假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了csdn这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。
抽象观察者(Observer)
/**
* 抽象观察者
* 定义了一个更新的方法
*/
public interface Observer {
void update(String message);
}
具体观察者(ConcrereObserver):WeChatUser
/**
* 具体观察者
* 微信用户是观察者,里面实现了更新的方法
*/
public class WeChatUser implements Observer{
private String name;//微信用户名
public WeChatUser(String name){
this.name=name;
}
@Override
public void update(String message) {
System.out.println(name+":"+message);
}
}
抽象被观察者(Subject):
/**
* 抽象被观察者
* 抽象主题,提供了attach、detach、notify三个方法
*/
public interface Subject {
/**
* 增加订阅者
* @param observer
*/
void attach(Observer observer);
/**
* 删除订阅者
* @param observer
*/
void detach(Observer observer);
/**
* 通知订阅者更新消息
* @param message
*/
void notify(String message);
}
具体被观察者(ConcreteSubject):SubscriptionSubject
/**
* 具体被观察者
* 微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
*/
public class SubscriptionSubject implements Subject{
//储存订阅公众号的微信用户
private List<Observer> wechatUserList = new ArrayList<Observer>();
@Override
public void attach(Observer observer) {
wechatUserList.add(observer);
}
@Override
public void detach(Observer observer) {
wechatUserList.remove(observer);
}
@Override
public void notify(String message) {
//给每一个微信用户发送消息
for (Observer observer:wechatUserList)
observer.update(message);
}
}
客户端调用:测试类
/**
* 客户端调用
*/
public class Client {
public static void main(String[] args) {
SubscriptionSubject subject = new SubscriptionSubject();
//创建微信用户
WeChatUser user1 = new WeChatUser("张三");
WeChatUser user2 = new WeChatUser("lxy");
WeChatUser user3 = new WeChatUser("王五");
//订阅公众号
subject.attach(user1);
subject.attach(user2);
subject.attach(user3);
//公众号更新发出消息给订阅的微信用户
subject.notify("csdn公众号中有内容更新了,请查看");
}
}
7.策略模式(Strategy)
在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。
策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。UML结构图如下:
其中,Context是上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用;Strategy是策略类,用于定义所有支持算法的公共接口;ConcreteStrategy是具体策略类,封装了具体的算法或行为,继承于Strategy。
环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。
策略模式,多种不同解决方案动态切换,起到改变对象行为的效果。
优点
1、可以动态的改变对象的行为
缺点
1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类
2、策略模式将造成产生很多策略类
案例
场景如下,刘备要到江东娶老婆了,走之前诸葛亮给赵云三个锦囊妙计,说是按天机拆开能解决棘手问题。场景中出现三个要素:三个妙计(具体策略类)、一个锦囊(环境类)、赵云(调用者)。
抽象策略类( Strategy )
/**
* 策略模式
* 抽象策略类
*/
public interface Strategy {
void operate();
}
三个实现类( ConcreteStrategy )
BackDoor
/**
* 实现类( ConcreteStrategy
* 妙计一:初到吴国
*/
public class BackDoor implements Strategy{
@Override
public void operate() {
System.out.println("找乔国老帮忙,让吴国太给孙权施加压力,使孙权不能杀刘备");
}
}
GivenGreenLight
/**
* 妙计二:求吴国太开绿灯放行
*/
public class GivenGreenLight implements Strategy{
@Override
public void operate() {
System.out.println("求吴国太开个绿灯,放行");
}
}
BlackEnemy
/**
* 妙计三:孙夫人断后,挡住追兵
*/
public class BlackEnemy implements Strategy{
@Override
public void operate() {
System.out.println("孙夫人断后,挡住追兵");
}
}
环境类(Context上下文)
/**
* 环境类( Context)
*/
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy=strategy;
}
public void setStrategy(Strategy strategy){
this.strategy=strategy;
}
public void operate(){
this.strategy.operate();
}
}
测试类
/**
* 根据不同的情况选择不同的策略(算法)
*/
public class Test {
public static void main(String[] args) {
Context context;
System.out.println("----------刚到吴国使用第一个锦囊---------------");
context=new Context(new BackDoor());
context.operate();
System.out.println("----------刘备乐不思蜀使用第二个锦囊---------------");
context.setStrategy(new GivenGreenLight());
context.operate();
System.out.println("----------孙权的追兵来了,使用第三个锦囊---------------");
context.setStrategy(new BlackEnemy());
context.operate();
}
}
8.装饰模式(Decorator)
装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。UML结构图如下:
其中,Component是抽象构件,定义一个对象接口,可以给这些对象动态地添加职责;ConreteComponent定义一个具体对象,也可以给这个对象添加一些职责;Decorator是装饰抽象类,实现接口或抽象方法;ConreteDecorator是具体装饰对象,起到给Component添加职责的功能。
在装饰模式中的角色有:
●抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
●具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。
●装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
●具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。
装饰模式的应用
- 何时使用
- 在不想增加很多子类的情况下扩展类的功能
- 方法
- 将具体功能职责划分,同时继承装饰者模式
- 优点
-
装饰类和被装饰类可以独立发展,而不会相互耦合。它有效地把类的核心职责和装饰功能分开了
-
装饰模式是继承关系的一个替代方案
-
装饰模式可以动态地扩展一个实现类的功能
-
装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
- 缺点
- 多层装饰比较复杂。比如我们现在有很多层装饰,出了问题,一层一层检查,最后发现是最里层的装饰出问题了,想想工作量都害怕。
- 由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
- 使用场景
- 需要扩展一个类的功能时
- 需要动态地给一个对象增加功能,并可以动态地撤销时
- 需要为一批的兄弟类进行改装或加装功能时
- 应用实例
- 旧机包装成新机,手机/电脑内部配件不变,只是换个外壳
- 换衣小游戏,人还是那个人,不断给她换衣服,还可以一层套一层的
- 孙悟空有72变,变成什么后就有了它的功能,但本质还是一只猴子
案例
孙悟空有七十二般变化,他的每一种变化都给他带来一种附加的本领。他变成鱼儿时,就可以到水里游泳;他变成鸟儿时,就可以在天上飞行。
本例中,Component的角色便由鼎鼎大名的齐天大圣扮演;ConcreteComponent的角色属于大圣的本尊,就是猢狲本人;Decorator的角色由大圣的七十二变扮演。而ConcreteDecorator的角色便是鱼儿、鸟儿等七十二般变化。
抽象构件角色Component
/**
* 大圣的尊号
*/
public interface TheGreatestSage {
void move();
}
具体装饰(ConcreteDecorator)角色:Monkey
public class Monkey implements TheGreatestSage{
@Override
public void move() {
System.out.println("Monkey Move");
}
}
装饰(Decorator)角色:Change
public class Change implements TheGreatestSage{
private TheGreatestSage theGreatestSage;
public Change(TheGreatestSage theGreatestSage){
this.theGreatestSage=theGreatestSage;
}
@Override
public void move() {
theGreatestSage.move();
}
}
具体装饰角色:Bird
public class Bird extends Change{
public Bird(TheGreatestSage theGreatestSage){
super(theGreatestSage);
}
@Override
public void move() {
System.out.println("Bird Move");
}
}
Fish
public class Fish extends Change{
public Fish(TheGreatestSage theGreatestSage){
super(theGreatestSage);
}
@Override
public void move() {
System.out.println("Fish Move");
}
}
测试类
public class Client {
public static void main(String[] args) {
TheGreatestSage theGreatestSage=new Monkey();
theGreatestSage.move();
TheGreatestSage bird = new Bird(theGreatestSage);
TheGreatestSage fish = new Fish(bird);
bird.move();
fish.move();
}
}
上面的例子中,系统把大圣从一只猢狲装饰成了一只鸟儿(把鸟儿的功能加到了猢狲身上),然后又把鸟儿装饰成了一条鱼儿(把鱼儿的功能加到了猢狲+鸟儿身上,得到了猢狲+鸟儿+鱼儿)。
9.外观模式(Facade)
为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
优点:
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
缺点:
- 不能很好地限制客户使用子系统类,很容易带来未知风险。
- 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。现在来分析其基本结构和实现方法。
模式的结构
外观(Facade)模式包含以下主要角色。
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
- 客户(Client)角色:通过一个外观角色访问各个子系统的功能。
其结构图如图所示:
案例
子系统角色中的类
ModuleA
public class ModuleA {
public void testA(){
System.out.println("调用ModuleA中的testA方法");
}
}
ModuleB
public class ModuleB {
public void testB(){
System.out.println("调用ModuleB中的testB方法");
}
}
ModuleC
public class ModuleC {
public void testC(){
System.out.println("调用ModuleC中的testC方法");
}
}
外观角色类Facade
/**
* 外观角色类
*/
public class Facade {
public void test()
{
ModuleA moduleA = new ModuleA();
moduleA.testA();
ModuleB moduleB = new ModuleB();
moduleB.testB();
ModuleC moduleC = new ModuleC();
moduleC.testC();
}
}
客户端角色类Client
/**
* 客户端角色类
*/
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.test();
}
}
Facade类其实相当于A、B、C模块的外观界面,有了这个Facade类,那么客户端就不需要亲自调用子系统中的A、B、C模块了,也不需要知道系统内部的实现细节,甚至都不需要知道A、B、C模块的存在,客户端只需要跟Facade类交互就好了,从而更好地实现了客户端和子系统中A、B、C模块的解耦,让客户端更容易地使用系统。
使用外观模式还有一个附带的好处,就是能够有选择性地暴露方法。一个模块中定义的方法可以分成两部分,一部分是给子系统外部使用的,一部分是子系统内部模块之间相互调用时使用的。有了Facade类,那么用于子系统内部模块之间相互调用的方法就不用暴露给子系统外部了。
10.桥接模式(Bridge)
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
当我们发现类有多层继承时就可以考虑使用桥接模式
意图:将抽象与实现解耦。
桥接模式主要应对的是由于实际的需要,某个类具有两个或者两个以上的维度变化(违反了SRP原则),如果只是用继承将无法实现这种需要,或者使得设计变得相当臃肿。
UML结构图如下:
Abstraction:定义抽象接口,拥有一个Implementor类型的对象引用
RefinedAbstraction:扩展Abstraction中的接口定义
Implementor:是具体实现的接口,Implementor和RefinedAbstraction接口并不一定完全一致,实际上这两个接口可以完全不一样Implementor提供具体操作方法,而Abstraction提供更高层次的调用
ConcreteImplementor:实现Implementor接口,给出具体实现
案例
交通工具在路上行驶,这里有两个维度的变化,首先交通工具的类型不同,其次路也分水泥路和柏油路。
抽象的路(Abstraction):Road
/**
* 抽象的路(Abstraction)
*/
public abstract class Road {
protected Vehicle vehicle;
public Road(Vehicle vehicle){
this.vehicle=vehicle;
}
public abstract void driveOnRoad();
}
具体的路(RefinedAbstraction)
CementRoad
/**
* 具体的路 水泥路
*/
public class CementRoad extends Road{
public CementRoad(Vehicle vehicle) {
super(vehicle);
}
@Override
public void driveOnRoad() {
super.vehicle.drive();
System.out.println("行驶在水泥路");
}
}
UnpavedRoad
/**
* 具体的路 石子路
*/
public class UnpavedRoad extends Road {
public UnpavedRoad(Vehicle vehicle){
super(vehicle);
}
@Override
public void driveOnRoad() {
super.vehicle.drive();
System.out.println("行驶在石子路");
}
}
交通工具(Implementor):Vehicle
/**
* 桥接模式
* 交通工具(Implementor)
*/
public interface Vehicle {
void drive();
}
具体的交通工具(ConcreteImplementor)
Bus
/**
* 具体的交通工具(ConcreteImplementor) Bus
*/
public class Bus implements Vehicle{
@Override
public void drive() {
System.out.println("公交车");
}
}
Car
/**
* 具体的交通工具(ConcreteImplementor) Car
*/
public class Car implements Vehicle{
@Override
public void drive() {
System.out.println("小汽车");
}
}
测试类
public class Main {
public static void main(String[] args) {
Road road = new CementRoad(new Car());
road.driveOnRoad();
Road road1 = new UnpavedRoad(new Bus());
road1.driveOnRoad();
}
}
效果及实现要点:
桥接模式使用对象见的组合关系解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
所谓抽象和实现沿着各自维度的变化,即“子类化”它们,得到各个子类之后,便可以任意它们,从而获得不同路上的不同其次。
桥接模式有时候类似于多继承方案,但是多继承方案往往违背了SRP(单一职责:每个类应该有一个责任,这个责任应该由类完全封装。它的所有服务应向责任狭义看齐)原则,复用性较差。桥接模式是比继承方案更好的解决方法。
桥接模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换而言之两个变化不会导致纵横交错的结果,并不一定要使用桥接模式。
使用场景
如果你不希望在抽象和实现部分采用固定的绑定关系,可以采用桥接模式,来把抽象和实现部分分开,然后在程序运行期间来动态的设置抽象部分需要用到的具体的实现,还可以动态切换具体的实现。
如果出现抽象部分和实现部分都应该可以扩展的情况,可以采用桥接模式,让抽象部分和实现部分可以独立的变化,从而可以灵活的进行单独扩展,而不是搅在一起,扩展一边会影响到另一边。
如果希望实现部分的修改,不会对客户产生影响,可以采用桥接模式,客户是面向抽象的接口在运行,实现部分的修改,可以独立于抽象部分,也就不会对客户产生影响了,也可以说对客户是透明的。
如果采用继承的实现方案,会导致产生很多子类,对于这种情况,可以考虑采用桥接模式,分析功能变化的原因,看看是否能分离成不同的纬度,然后通过桥接模式来分离它们,从而减少子类的数目。
Jdk中的桥接模式:JDBC
JDBC连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不动,原因就是JDBC提供了统一接口,每个数据库提供各自的实现,用数据库驱动的程序来桥接就行了
11.组合模式(Composite)
组合模式(Composite),将对象组合成树形结构以表示“部分-整体”的层次结构,用户对单个对象和组合对象的使用具有一致性。
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下:
由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。
这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。
组合模式的主要优点有:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码。
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”。
其主要缺点是:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系。
- 不容易限制容器中的构件。
- 不容易用继承的方法来增加构件的新功能。
组合模式包含以下主要角色。
- 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
- 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
- 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
组合模式有两种不同的实现,分别为透明模式和安全模式
透明模式
透明模式是把组合使用的方法放到抽象类中,不管叶子对象还是树枝对象都有相同的结构,这样做的好处就是叶子节点和树枝节点对于外界没有区别,它们具备完全一致的行为接口。但因为Leaf类本身不具备add()、remove()方法的功能,所以实现它是没有意义的。UML结构图如下:
安全模式
安全模式是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全。但由于不够透明,所以树叶节点和树枝节点将不具有相同的接口,客户端的调用需要做相应的判断,带来了不便。UML结构图如下:
组合模式的应用
1. 何时使用
- 想表达“部分-整体”层次结构(树形结构)时
- 希望用户忽略组合对象与单个对象的不同,用户将统一的使用组合结构中的所有对象
- 方法
- 树枝和叶子实现统一接口,树枝内部组合该接口
3. 优点
- 高层模块调用简单。一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,高层模块不必关心自己处理的是单个对象还是整个组合结构。
- 节点自由增加
4. 缺点
- 使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒转原则
5. 使用场景
- 维护和展示部分-整体关系的场景(如树形菜单、文件和文件夹管理)
- 从一个整体中能够独立出部分模块或功能的场景
6. 应用实例
- 文本编辑时,可以单个字编辑,也可以整段编辑,还可以全文编辑
- 文件复制时,可以一个一个文件复制,也可以整个文件夹复制
案例
以公司的层级结构为例,先看一下这个例子中该公司的层级结构。
这种部分与整体的关系,我们就可以考虑使用组合模式,下面采用组合模式的透明模式对其实现,UML图如下:
抽象构件(Component)角色:Company
public abstract class Company {
protected String name;
public Company(String name){
this.name=name;
}
public abstract void add(Company company);
public abstract void remove(Company company);
public abstract void display(int depth);
public abstract void lineOfDuty();
}
树枝构件(Composite)角色:ConcreteCompany
/**
* 此为树枝节点,实现添加、移除、显示和履行职责四种方法
*/
public class ConcreteCompany extends Company{
private List<Company> companyList=new ArrayList<Company>();
public ConcreteCompany(String name){
super(name);
}
@Override
public void add(Company company) {
this.companyList.add(company);
}
@Override
public void remove(Company company) {
this.companyList.remove(company);
}
@Override
public void display(int depth) {
//输出树形结构
for (int i=0;i<depth;i++){
System.out.print("-");
}
System.out.println(name);
//下级遍历
for (Company company:companyList){
//递归调用
company.display(depth+1);
}
}
@Override
public void lineOfDuty() {
//职责遍历
for (Company company:companyList){
company.lineOfDuty();
}
}
}
树叶构件(Leaf)角色
FinanceDepartment
/**
* 财务部
* 叶子节点,add和remove方法空实现。
*/
public class FinanceDepartment extends Company{
public FinanceDepartment(String name) {
super(name);
}
@Override
public void add(Company company) {
}
@Override
public void remove(Company company) {
}
@Override
public void display(int depth) {
//输出树形结构的子节点
for(int i=0; i<depth; i++) {
System.out.print('-');
}
System.out.println(name);
}
@Override
public void lineOfDuty() {
System.out.println(name + " : 公司财务收支管理");
}
}
HRDepartment
/**
* 人力资源部
* 叶子节点,add和remove方法空实现
*/
public class HRDepartment extends Company{
public HRDepartment(String name) {
super(name);
}
@Override
public void add(Company company) {
}
@Override
public void remove(Company company) {
}
@Override
public void display(int depth) {
//输出树形结构的子节点
for (int i=0;i<depth;i++){
System.out.print("-");
}
System.out.println(name);
}
@Override
public void lineOfDuty() {
System.out.println(name+" : 员工招聘培训管理");
}
}
客户端(测试类)
/**
* Client客户端
*/
public class Client {
public static void main(String[] args) {
//总公司
ConcreteCompany root=new ConcreteCompany("北京总公司");
root.add(new HRDepartment("总公司人力资源部"));
root.add(new FinanceDepartment("总公司财务部"));
//分公司
ConcreteCompany company = new ConcreteCompany("上海华东分公司");
company.add(new HRDepartment("华东分公司人力资源部"));
company.add(new FinanceDepartment("华东分公司财务部"));
root.add(company);
//办事处
ConcreteCompany company1 = new ConcreteCompany("南京办事处");
company1.add(new HRDepartment("南京办事处人力资源部"));
company1.add(new FinanceDepartment("南京办事处财务部"));
company.add(company1);
ConcreteCompany company2 = new ConcreteCompany("杭州办事处");
company2.add(new HRDepartment("杭州办事处人力资源部"));
company2.add(new FinanceDepartment("杭州办事处财务部"));
company.add(company2);
System.out.println("结构图:");
root.display(1);
System.out.println("\n职责:");
root.lineOfDuty();
}
}
12.享元模式(Flyweight)
说到享元模式,第一个想到的应该就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,所以说享元模式是池技术的重要实现方式。
享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。
- 内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变;
- 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。
比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。
享元模式的本质是缓存共享对象,降低内存消耗。
UML结构图如下:
- 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
- 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
- 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
- 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
其主要缺点是:
- 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
- 读取享元模式的外部状态会使得运行时间稍微变长。
案例
在一个象棋游戏中,每个对局都会有一个棋局对象,同时在线可能会有成千上万的棋局对象,每个棋局又有多个对象,那就需要数万乃至数十万的棋子对象,这样就会对内存造成极大的消耗,影响程序性能。但是在每个棋局中的棋子的种类、颜色等属性都是固定不变的,不同的两个棋局中的棋子对象的区别就只是属于不同的棋局和拥有不同坐标,因此可以创建一个模板棋局,其中包含所有的棋子对象,将这个对象定义成一个静态变量,以后新增的棋局对象都可以使用这个棋局对象作为模板,共享使用这个棋局和其中的棋子对象,只需要在不同的棋局中修改棋子的坐标即可。
//棋子享元类,不同棋盘中不同坐标的棋子的相同的属性对象。
public class ChessUnit {
public int id;
public String color;
public String chessName;
public ChessUnit (int id, String color, String chessName) {
this.id = id;
this.color = color;
this.chessName = chessName;
}
}
//真正的棋子类
public class Chess {
private ChessUnit chessUnit;
private int x;
private int y;
public Chess(ChessUnit chessUnit, int x, int y){
this.chessUnit = chessUnit;
this.x = x;
this.y = y;
}
}
//提供一个工厂类,保存不变的那些固定的不变的要被共享的享元对象,用静态对象保存
public class ChessUnitFactory {
private static final Map<Integer, ChessUnit> chesses = new HashMap<Integer, ChessUnit>();
static {
chesses.put(1, new ChessUnit(1, "red", "马"));
chesses.put(2, new ChessUnit(1, "red", "将"));
chesses.put(3, new ChessUnit(1, "red", "士"));
chesses.put(4, new ChessUnit(1, "red", "象"));
}
public static ChessUnit getChessByid(int chessId){
return chesses.get(chessId);
}
}
//一个棋盘类,在构造方法中调用init方法,利用保存好的静态变量来初始化对象,节约内存空间。
public class ChessBoard {
private Map<Integer, Chess> chessBoard = new HashMap<Integer, Chess>();
public ChessBoard(){
init();
}
public void init() {
chessBoard.put(1, new Chess(ChessUnitFactory.getChessByid(1),123,32));
chessBoard.put(2, new Chess(ChessUnitFactory.getChessByid(2),123,32));
chessBoard.put(3, new Chess(ChessUnitFactory.getChessByid(3),123,32));
chessBoard.put(4, new Chess(ChessUnitFactory.getChessByid(4),123,32));
}
}
当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。
适合采用享元模式的情形:
- 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
- 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
- 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
13.建造者模式(Builder)
当我们在外面饭店吃饭时,比如点个水煮肉片,这家店可能会辣一点、那家店可能会咸一点、对面那家可能放青菜、隔壁那家可能放菠菜,每家店做出来的都不一样,明明都是水煮肉片却有不同的做法,如果都一样就不会说这家难吃那家好吃了。那再看快餐店,比如KFC,我们点个鸡肉汉堡,所有人不管在哪家店,做法、味道都是一样的,为什么呢,因为它用料、时间、温度等都是严格规定的,我们只需要下订单就行了,这就是一个建造者模式。
建造者模式(Builder),将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。UML结构图如下:
导演类/指挥者(Director):负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变的部分。
抽象建造者(Builder):引入抽象建造者的目的,是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品。
具体建造者(ConcreteBuilder):实现抽象类的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品。
产品类(Product):一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有比较多的代码量。在本类图中,产品类是一个具体的类,而非抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。
建造者模式的应用
- 何时使用
- 一个基本部件不会变,而其组合经常变化的时候
- 优点
- 封装性。是客户端不必知道产品内部组成的细节。
- 建造者独立,易扩展。
- 便于控制细节风险。可以对建造过程逐步细化,而不对其他模块产生任何影响。
- 缺点
- 产品必须有共同点,范围有限制。
- 如果内部变化复杂,会有很多建造类。
- 使用场景
- 相同的方法,不同的执行顺序,产生不同的事件结果时。
- 需要生成的对象具有复杂的内部结构时。
- 多个部件或零件,都可以装配到一个对象中,但产生的结果又不相同时。
- 与工厂模式的区别
- 建造者模式更关注于零件装配的顺序
案例
KFC点套餐的例子,我们点餐可以点一个汉堡和一个冷饮,汉堡可以是鸡肉汉堡、虾堡等等,是装在盒子中的,冷饮可以是可乐、雪碧等等,是装在瓶子中的。下面我们来用建造者模式对其进行组合,用户只需提交订单即可,UML图如下:
Item
/**
* 创建一个表示食物条目和食物包装的接口。
*/
public interface Item {
//获取食物名称
public String getName();
//获取包装
public Packing packing();
//获取价格
public float getPrice();
}
Packing
public interface Packing {
//获取包装类型
public String getPack();
}
Wrapper:
实现Packing接口的实现类
/**
* 包装类:Wrapper为纸盒包装
*/
public class Wrapper implements Packing {
@Override
public String getPack() {
return "纸盒";
}
}
Bottle
/**
* 包装类:Bottle为瓶装
*/
public class Bottle implements Packing{
@Override
public String getPack() {
return "塑料瓶";
}
}
Burger
/**
* 创建实现Item接口的抽象类。Burger为汉堡
*/
public abstract class Burger implements Item{
@Override
public Packing packing() {
return new Wrapper();
}
@Override
public abstract float getPrice();
}
Drink
/**
* 创建实现Item接口的抽象类。Drink为饮品
*/
public abstract class Drink implements Item{
@Override
public Packing packing() {
return new Bottle();
}
@Override
public abstract float getPrice();
}
具体实现类
Burger1
/**
* 创建扩展了Burger的具体实现类
*/
public class Burger1 extends Burger{
@Override
public float getPrice() {
return 23.0f;
}
@Override
public String getName() {
return "鸡肉汉堡";
}
}
Burger2
public class Burger2 extends Burger{
@Override
public float getPrice() {
return 25.0f;
}
@Override
public String getName() {
return "牛肉汉堡";
}
}
Drink1
public class Drink1 extends Drink{
@Override
public float getPrice() {
return 6.0f;
}
@Override
public String getName() {
return "可口可乐";
}
}
Drink2
public class Drink2 extends Drink{
@Override
public float getPrice() {
return 10.0f;
}
@Override
public String getName() {
return "橙汁";
}
}
Meal
public class Meal {
private List<Item> items=new ArrayList<>();
public void addItem(Item item){
items.add(item);
}
//获取总消费
public float getCost(){
float cost=0.0f;
for (Item item : items) {
cost+=item.getPrice();
}
return cost;
}
public void showItem(){
for (Item item : items) {
System.out.print("餐品:" + item.getName());
System.out.print(",包装:" + item.packing().getPack());
System.out.println(",价格:¥" + item.getPrice());
}
}
}
指挥者:MealBuilder
/**
* 指挥者
*/
public class MealBuilder {
//订单1
public Meal order1(){
Meal meal = new Meal();
meal.addItem(new Burger1());
meal.addItem(new Burger2());
meal.addItem(new Drink1());
return meal;
}
//订单2
public Meal order2(){
Meal meal = new Meal();
meal.addItem(new Burger2());
meal.addItem(new Drink2());
return meal;
}
}
测试类
public class Client {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();
//获取第一个订单
Meal order1 = mealBuilder.order1();
System.out.println("------order1-------");
order1.showItem();
System.out.println("总额:¥" + order1.getCost());
//获取第二个订单
Meal order2 = mealBuilder.order2();
System.out.println("------order2-------");
order2.showItem();
System.out.println("总额:¥" + order2.getCost());
}
}
14.原型模式(Prototype)
当系统中需要大量创建相同或者相似的对象时,就可以通过“原型设计模式”来实现。原型模式是“创建型设计模式”中的一种。
原型模式是简单程度仅次于单例模式的简单模式,它的定义可以简单理解为对象的拷贝,通过拷贝的方式创建一个已有对象的新对象,这就是原型模式。
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
原型模式的优点:
- Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
原型模式的缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当
原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
其结构图如图所示:
原型模式的克隆分为浅克隆和深克隆。
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
案例
用原型模式生成“三好学生”奖状。
同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,属于相似对象的复制,同样可以用原型模式创建,然后再做简单修改就可以了。图 所示是三好学生奖状生成器的结构图。
抽象原型类为java自带的Cloneable接口
具体原型类:Citation
/**
* 原型模式
* 奖状类
* Cloneable:java.lang包自带的,实现Cloneable接口就可实现对象的浅克隆
*/
public class Citation implements Cloneable{
String name;
String info;
String college;
public Citation(String name, String info, String college){
this.name=name;
this.info=info;
this.college=college;
System.out.println("奖状创建成功!");
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
void display(){
System.out.println(name+info+college);
}
public Object clone() throws CloneNotSupportedException {
System.out.println("奖状拷贝成功!");
return (Citation)super.clone();
}
}
访问类:ProtoTypeCitation
public class ProtoTypeCitation {
public static void main(String[] args) throws CloneNotSupportedException {
Citation obj1 = new Citation("张三", "同学:在2021学年第一学期中表现优秀,被评为三好学生。", "西交大");
obj1.display();
Citation obj2 = (Citation)obj1.clone();
obj2.setName("lxy");
obj2.display();
}
}
原型模式通常适用于以下场景。
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
- 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。
15.解释器模式(Interpreter)
解释器模式(Interpreter),给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
就比如正则表达式,它就是解释器模型的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式
如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子,这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
解释器模式包含以下主要角色。
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
解释器模式的结构图如图所示:
Expression
/**
* 抽象表达式类
*/
public interface Expression {
public boolean interpret(String info);
}
AndExpression
/**
* 非终结符表达式类
*/
public class AndExpression implements Expression{
private Expression city=null;
private Expression person=null;
public AndExpression(Expression city,Expression person){
this.city=city;
this.person=person;
}
@Override
public boolean interpret(String info) {
String s[] = info.split("的");
return city.interpret(s[0]) && person.interpret(s[1]);
}
}
TerminalExpression
/**
* 终结符表达式类
*/
public class TerminalExpression implements Expression{
private Set<String> set=new HashSet<String>();
public TerminalExpression(String[] data){
for (int i=0;i<data.length;i++)
set.add(data[i]);
}
@Override
public boolean interpret(String info) {
if (set.contains(info)){
return true;
}
return false;
}
}
Context
/**
* 环境类
*/
public class Context {
private String[] citys={"西安","咸阳"};
private String[] persons={"老人","妇女","儿童"};
private Expression cityPerson;
public Context(){
Expression city = new TerminalExpression(citys);
Expression person = new TerminalExpression(persons);
cityPerson=new AndExpression(city,person);
}
public void freeRide(String info){
boolean ok=cityPerson.interpret(info);
if (ok)
System.out.println("您是"+info+",您本次乘车免费!");
else
System.out.println(info+",您不是免费人员,本次乘车扣费2元!");
}
}
测试类
public class Main {
public static void main(String[] args) {
Context context1 = new Context();
context1.freeRide("西安的老人");
Context context2 = new Context();
context2.freeRide("咸阳的儿童");
Context context3 = new Context();
context3.freeRide("西安的年轻人");
Context context4 = new Context();
context4.freeRide("北京的老人");
}
}
16.模板方法模式(TemplateMethod)
模板方法模式(TemplateMethod),定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
该模式的主要优点如下:
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
该模式的主要缺点如下:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
- 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
模板方法模式的结构图如图所示:
抽象类/抽象模板(Abstract Class)
抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
- 抽象方法:在抽象类中声明,由具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
具体子类/具体实现(Concrete Class)
具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
案例
小时候数学随堂测验都是老师在黑板上抄题目,然后学生先抄题目,再做答案。用程序实现这个过程。
UML图:
TestPaper
/**
* 黑板上的试卷-金庸小说考题试卷
*/
public abstract class TestPaper {
public abstract String Answer1();
public void TestQuestion1(){
System.out.println("第1题");
System.out.println("答案"+Answer1());
}
public abstract String Answer2();
public void TestQuestion2(){
System.out.println("第2题");
System.out.println("答案"+Answer2());
}
public abstract String Answer3();
public void TestQuestion3(){
System.out.println("第3题");
System.out.println("答案"+Answer3());
}
}
TestPaperA
/**
* 学生甲抄的试卷
*/
public class TestPaperA extends TestPaper{
@Override
public String Answer1() {
return "b";
}
@Override
public String Answer2() {
return "c";
}
@Override
public String Answer3() {
return "d";
}
}
TestPaperB
/**
* 学生乙抄的试卷
*/
public class TestPaperB extends TestPaper{
@Override
public String Answer1() {
return "b";
}
@Override
public String Answer2() {
return "b";
}
@Override
public String Answer3() {
return "a";
}
}
测试类
public class Main {
public static void main(String[] args) {
System.out.println("学生甲抄的试卷:");
TestPaper studentA = new TestPaperA();
studentA.TestQuestion1();
studentA.TestQuestion2();
studentA.TestQuestion3();
System.out.println("学生乙抄的试卷:");
TestPaper studentB = new TestPaperB();
studentB.TestQuestion1();
studentB.TestQuestion2();
studentB.TestQuestion3();
}
}
模板方法模式是通过把不变行为搬移到超类,去除子类中的重复代码来体现它的优势。当不变和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。我们通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复的不变行为的纠缠。
17.迭代器模式(Iterator)
在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如数据结构中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。
迭代器模式,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java中的 Collection、List、Set、Map 等都包含了迭代器。
迭代器(Iterator)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式,其主要优点如下。
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
其主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性。
在日常开发中,我们几乎不会自己写迭代器。除非需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供的 API 完全够用。
迭代器模式主要包含以下角色。
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
其结构图如图 所示:
案例
抽象迭代器:Iterator
/**
* 抽象迭代器
*/
public interface Iterator {
Object first();
Object next();
boolean hasNext();
}
具体迭代器:ConcreteIterator
/**
* 具体迭代器
*/
public class ConcreteIterator implements Iterator{
private List<Object> list=null;
private int index=-1;
public ConcreteIterator(List<Object> list){
this.list=list;
}
@Override
public Object first() {
index=0;
Object obj = list.get(index);
return obj;
}
@Override
public Object next() {
Object obj=null;
if (this.hasNext()){
obj=list.get(++index);
}
return obj;
}
@Override
public boolean hasNext() {
if (index<list.size()-1){
return true;
}
else {
return false;
}
}
}
抽象聚合:Aggregate
/**
* 抽象聚合
*/
public interface Aggregate {
void add(Object object);
void remove(Object object);
Iterator getIterator();
}
具体聚合:ConcreteAggregate
/**
* 具体聚合
*/
public class ConcreteAggregate implements Aggregate{
private List<Object> list = new ArrayList<Object>();
@Override
public void add(Object object) {
list.add(object);
}
@Override
public void remove(Object object) {
list.remove(object);
}
@Override
public Iterator getIterator() {
return new ConcreteIterator(list);
}
}
测试类
public class Main {
public static void main(String[] args) {
Aggregate aggregate = new ConcreteAggregate();
aggregate.add("西安交大");
aggregate.add("西北大学");
aggregate.add("西电");
System.out.print("聚合的内容有:");
Iterator iterator = aggregate.getIterator();
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.print(obj.toString()+"\t");
}
Object o = iterator.first();
System.out.println("\nFirst:"+o.toString());
}
}
代器模式通常在以下几种情况使用。
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
由于聚合与迭代器的关系非常密切,所以大多数语言在实现聚合类时都提供了迭代器类,因此大数情况下使用语言中已有的聚合类的迭代器就已经够了。
18.责任链模式(Chain of Responsibility)
责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。
职责链模式主要包含以下角色。
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。
其结构图如图 1 所示。客户端可按图 2 所示设置责任链。
案例
假如规定学生请假小于或等于 2 天,班主任可以批准;小于或等于 7 天,系主任可以批准;小于或等于 10 天,院长可以批准;其他情况不予批准;这个实例适合使用职责链模式实现。
首先,定义一个领导类(Leader),它是抽象处理者,包含了一个指向下一位领导的指针 next 和一个处理假条的抽象处理方法 handleRequest(int LeaveDays);然后,定义班主任类(ClassAdviser)、系主任类(DepartmentHead)和院长类(Dean),它们是抽象处理者的子类,是具体处理者,必须根据自己的权力去实现父类的 handleRequest(int LeaveDays) 方法,如果无权处理就将假条交给下一位具体处理者,直到最后;客户类负责创建处理链,并将假条交给链头的具体处理者(班主任)
抽象处理者:Leader
/**
* 抽象处理者:领导类
*/
public abstract class Leader {
private Leader next;
public void setNext(Leader next){
this.next= next;
}
public Leader getNext(){
return next;
}
//处理请求的方法
public abstract void handleRequest(int LeaveDays);
}
具体处理者:ClassAdviser
/**
* 具体处理者1:班主任类
*/
public class ClassAdviser extends Leader{
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays<=2){
System.out.println("班主任批准您请假" + LeaveDays + "天。");
}else {
if (getNext()!=null){
getNext().handleRequest(LeaveDays);
}else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
DepartmentHead
/**
* 具体处理者2:系主任类
*/
public class DepartmentHead extends Leader{
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays<=7){
System.out.println("系主任批准您请假" + LeaveDays + "天。");
}
else {
if (getNext()!=null){
getNext().handleRequest(LeaveDays);
}else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
Dean
/**
* 具体处理者3:院长类
*/
public class Dean extends Leader{
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays<=10){
System.out.println("院长批准您请假" + LeaveDays + "天。");
}else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
DeanOfStudies
/**
* 具体处理者4:教务处长类
*/
public class DeanOfStudies extends Leader{
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 20) {
System.out.println("教务处长批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
测试类
public class LeaveApprovalTest {
public static void main(String[] args) {
//组装责任链
Leader teacher1 = new ClassAdviser();
Leader teacher2=new DepartmentHead();
Leader teacher3=new Dean();
Leader teacher4=new DeanOfStudies();
teacher1.setNext(teacher2);
teacher2.setNext(teacher3);
teacher3.setNext(teacher4);
//提交请求
teacher1.handleRequest(15);
}
}
责任链模式通常在以下几种情况使用。
- 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
- 可动态指定一组对象处理请求,或添加新的处理者。
- 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
19.命令模式(Command)
命令(Command)模式的定义:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
命令模式解决了方法的请求者与实现者解耦
结构图如图所示:
命令模式包含以下主要角色。
- 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
- 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
- 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
案例
Command
/**
* 命令接口
*/
public interface Command {
void execute();
}
实现者/接收者(Receiver):Light
/**
* 灯 (Receiver) 命令的真正执行者
*/
public class Light {
public void on(){
System.out.println("开灯");
}
public void off(){
System.out.println("关灯");
}
}
具体命令类(Concrete Command):LightOnCommand
/**
* 开灯命令 (LightOnCommand )
*/
public class LightOnCommand implements Command{
private Light light;
public LightOnCommand(Light light){
this.light=light;
}
@Override
public void execute() {
light.on();
}
}
invoke:CommandController
/**
* 遥控器
* 命令调用者 invoke
*/
public class CommandController {
private Command command;
public void setCommand(Command command){
this.command=command;
}
/**
* 遥控器 按钮按下
*/
public void buttonWasPressed(){
command.execute();
}
}
测试类
public class MainClient {
public static void main(String[] args) {
CommandController commandController = new CommandController();
Light light = new Light();
LightOnCommand lightOnCommand = new LightOnCommand(light);
commandController.setCommand(lightOnCommand);
commandController.buttonWasPressed();
}
}
命令模式的主要优点如下。
- 通过引入中间件(抽象接口)降低系统的耦合度。
- 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo(撤销)和 Redo (恢复)操作。
- 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。
其缺点是:
- 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
- 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。
20.备忘录模式(Memento)
备忘录模式(Memento): 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存着这个状态。这样以后就可将该对象恢复到原先保存的状态。
备忘录模式的主要角色如下:
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento) 角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录模式的结构图如图 所示:
案例
游戏存档
发起人(Originator):GameRole
/**
* 游戏角色
* 简单记录了游戏角色的生命力、攻击力、防御力
* 通过saveState()方法来保存当前状态,通过recoveryState()方法来恢复角色状态。
*/
public class GameRole {
//生命力
private int vit;
//攻击力
private int atk;
//防御力
private int def;
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
//状态显示
public void stateDisplay(){
System.out.println("角色当前状态:");
System.out.println("体力:" + this.vit);
System.out.println("攻击力:" + this.atk);
System.out.println("防御力: " + this.def);
System.out.println("-----------------");
}
//获得初始状态
public void getInitState(){
this.vit=100;
this.atk=100;
this.def=100;
}
//战斗后
public void fight(){
this.vit=0;
this.atk=0;
this.def=0;
}
//保存角色状态
public RoleStateMemento saveState(){
return new RoleStateMemento(vit,atk,def);
}
//恢复角色状态
public void recoveryState(RoleStateMemento memento){
this.vit = memento.getVit();
this.atk = memento.getAtk();
this.def = memento.getDef();
}
}
备忘录(Memento):RoleStateMemento
/**
* 备忘录类,用于存储角色状态
*/
public class RoleStateMemento {
//生命力
private int vit;
//攻击力
private int atk;
//防御力
private int def;
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
管理者(Caretaker):
/**
* 备忘录管理者
*/
public class RoleStateCaretaker {
private RoleStateMemento memento;
public RoleStateMemento getMemento() {
return memento;
}
public void setMemento(RoleStateMemento memento) {
this.memento = memento;
}
}
测试类
public class Client {
public static void main(String[] args) {
//打boss前
GameRole gameRole = new GameRole();
gameRole.getInitState();
gameRole.stateDisplay();
//保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setMemento(gameRole.saveState());
//打boss失败
gameRole.fight();
gameRole.stateDisplay();
//恢复状态
gameRole.recoveryState(roleStateCaretaker.getMemento());
gameRole.stateDisplay();
}
}
使用场景
- 需要保存和恢复数据的相关场景
- 提供一个可回滚的操作,如ctrl+z、浏览器回退按钮、Backspace键等
- 需要监控的副本场景
21.状态模式(State)
状态模式(State):当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类
状态模式包含以下主要角色:
环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
其结构图如图 所示:
案例
实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,用状态模式来实现这个程序。
环境类:ScoreContext
/**
* 环境类
*/
public class ScoreContext {
private AbstractState state;
public ScoreContext(){
state=new LowState(this);
}
public AbstractState getState() {
return state;
}
public void setState(AbstractState state) {
this.state = state;
}
public void add(int score){
state.addScore(score);
}
}
抽象状态:AbstractState
/**
* 抽象状态类
*/
public abstract class AbstractState {
//环境
protected ScoreContext hj;
//状态名
protected String stateName;
//分数
protected int score;
//检查当前状态
public abstract void checkState();
public void addScore(int x){
score+=x;
System.out.println("加上:" + x + "分,\t当前分数:" + score);
checkState();
System.out.println("分,\t当前状态:" + hj.getState().stateName);
}
}
具体状态类
LowState
/**
* 具体状态类:不及格
*/
public class LowState extends AbstractState{
public LowState(ScoreContext h) {
hj=h;
stateName="不及格";
score=0;
}
public LowState(AbstractState state){
hj=state.hj;
stateName="不及格";
score=state.score;
}
@Override
public void checkState() {
if (score>=90){
hj.setState(new HighState(this));
}else if(score>=60){
hj.setState(new MiddleState(this));
}
}
}
MiddleState
/**
* 具体状态类:中等
*/
public class MiddleState extends AbstractState{
public MiddleState(AbstractState state) {
hj=state.hj;
stateName="中等";
score=state.score;
}
@Override
public void checkState() {
if (score<60){
hj.setState(new LowState(this));
}else if (score>=90){
hj.setState(new HighState(this));
}
}
}
HighState
/**
* 具体状态类:优秀
*/
public class HighState extends AbstractState{
public HighState(AbstractState state) {
hj=state.hj;
stateName="优秀";
score=state.score;
}
@Override
public void checkState() {
if (score < 60) {
hj.setState(new LowState(this));
} else if (score < 90) {
hj.setState(new MiddleState(this));
}
}
}
测试类
public class Test {
public static void main(String[] args) {
ScoreContext scoreContext = new ScoreContext();
System.out.println("学生成绩状态测试:");
scoreContext.add(30);
scoreContext.add(40);
scoreContext.add(25);
scoreContext.add(-15);
scoreContext.add(-25);
}
}
22.访问者模式(Visitor)
访问者模式(Visitor):封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作
访问者模式包含以下主要角色。
- 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
- 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
- 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
- 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
其结构图如图所示:
案例
Action
/**
* 抽象的状态类,主要声明以下两个方法。
* 人只分男人和女人,这个性别的分类是稳定的,
* 所以可以在状态类中,增加“男人反应”和“女人反应”两个方法,方法个数是稳定的,不会容易发生变化。
*/
public abstract class Action {
//得到男人的结论或反应
public abstract void getManConclusion(Man man);
//得到女人的结论或反应
public abstract void getWomanConclusion(Woman woman);
}
Success
/**
* Action类的具体实现类
*/
public class Success extends Action{
@Override
public void getManConclusion(Man man) {
System.out.println("男人成功...");
}
@Override
public void getWomanConclusion(Woman woman) {
System.out.println("女人成功...");
}
}
Fail
public class Fail extends Action{
@Override
public void getManConclusion(Man man) {
System.out.println("男人失败...");
}
@Override
public void getWomanConclusion(Woman woman) {
System.out.println("女人失败...");
}
}
Person
/**
* 人的抽象类。只有一个“接受”的抽象方法,它是用来获得“状态”对象的
*/
public abstract class Person {
//接受
public abstract void accept(Action action);
}
Man
/**
* Person类的具体实现类
*/
public class Man extends Person{
@Override
public void accept(Action action) {
action.getManConclusion(this);
}
}
Woman
public class Woman extends Person{
@Override
public void accept(Action action) {
action.getWomanConclusion(this);
}
}
ObjectStructure
/**
* 结构对象
*/
public class ObjectStructure {
private List<Person> elements = new LinkedList<>();
//增加
public void attach(Person person) {
elements.add(person);
}
//移除
public void detach(Person person) {
elements.remove(person);
}
//查看显示
public void display(Action action) {
for (Person person : elements) {
person.accept(action);
}
}
}
测试类
public class Client {
public static void main(String[] args) {
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);
}
}
23.中介者模式(Mediator)
中介者模式(Mediator):用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
多个类相互耦合,形成网状结构时,将网状结构分离为星型结构
中介者模式包含以下主要角色。
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
中介者模式的结构图如图所示:
案例
抽象中介者:UnitedNations
/**
* 联合国机构
* 抽象中介者
*/
public abstract class UnitedNations {
public abstract void declare(String message, Country country);
}
具体中介者:UnitedNationsSecurityCouncil
/**
* 具体中介者角色,继承抽象中介者,重写其declare()方法,对不同国家发送不同信息
*/
public class UnitedNationsSecurityCouncil extends UnitedNations{
private USA usa;
private China china;
public void setUsa(USA usa) {
this.usa = usa;
}
public void setChina(China china) {
this.china = china;
}
@Override
public void declare(String message, Country country) {
if (country==china){
china.getMessage(message);
}else {
usa.getMessage(message);
}
}
}
Country
/**
* 国家类
* 抽象国家类,需声明中介者角色
*/
public class Country {
protected UnitedNations unitedNations;
public Country(UnitedNations unitedNations) {
this.unitedNations = unitedNations;
}
}
USA
/**
* 具体国家类,继承了抽象国家类
*/
public class USA extends Country {
public USA(UnitedNations unitedNations) {
super(unitedNations);
}
public void declare(String message) {
unitedNations.declare(message, this);
}
public void getMessage(String message) {
System.out.println("美国获得对方信息:" + message);
}
}
China
public class China extends Country{
public China(UnitedNations unitedNations) {
super(unitedNations);
}
public void declare(String message) {
unitedNations.declare(message, this);
}
public void getMessage(String message) {
System.out.println("中国获得对方信息:" + message);
}
}
测试类
/**
* 实例化一个联合国安理会对象,两国通过联合国安理会进行对话。
*/
public class Client {
public static void main(String[] args) {
UnitedNationsSecurityCouncil UNSC = new UnitedNationsSecurityCouncil();
USA usa = new USA(UNSC);
China china = new China(UNSC);
UNSC.setUsa(usa);
UNSC.setChina(china);
usa.declare("不会使用核武器");
china.declare("不准研制核武器");
}
}