一 概述
设计模式是针对面向对象设计类语言在程序设计中的一类设计思想。这些思想是前人在程序设计中总结的一些经验,它能够提高代码的可读性、可维护性、稳健性和安全性。到目前为止,前人共总结了23种常用的设计模式,并且这23种设计模式必须遵循一些设计原则,即七大设计原则。本文只涉及一些常用设计模式。
相信每一个程序员都知道设计模式这个概念,但是又有很多人在实际业务中不能够熟练的使用设计模式,其原因有二,第一是没有实际的工作经验或者经验比较少,很难将设计模式和实际业务场景关联起来;第二种是没有系统全面的学习或者真正的理设计模式。不能深入理解的原因是网上有太多的相关文章,大多数的案例都是比较复杂难懂。基于此,下面将以最简单易懂的案例、最直白、最简洁的表达将系统的阐述常用设计模式,并且部分设计模式会结合源码说明,使读者不仅学会了设计模式,还对一些源码有了全面和深刻的认识。本文目前只有十种,但是,随着笔者的不断学习,会持续维护和更新本文,在此过程中,难免有些不全面或者不正确的地方,望读者海涵并纠正,楼主不胜感激。
二 七大设计原则
1. 开闭原则 (Open-Closed Principle, OCP)
开闭原则表示的是类或者模块应对扩展开放,对修改关闭。在软件设计中,经常会在原有的功能上增加一些新的 需求,举个例子:
比如有一个电商系统,其中一个查询账单的类 BillInquiry ,此类中一个查询方法的=部分代码如下:
1 if("oneMonth".equals(type)){ 2 billInquiryDao.findOneMonth(); 3 }else if("halfYear".equals(type)){ 4 billInquiryDao.findHalfYear(); 5 }
此方法可以查查询最近一个月的账单和最近半年的账单,但是如果现在需要增加查询一年的账单,此方法的逻辑就需要修改,就违背了开闭原则。对于此种情况,可以利用抽象类将所有的查询功能封装在抽象类中,需要哪种查询方式,只需要在抽象类中增加一个方法即可。
开闭原则也是以下六种原则的基础,其他原则也需要遵循开闭原则。
2. 单一职责原则 (Single Responsibility Principle, SRP)
单一职责原则就是一个类只负责一个功能领域中的相应职责,即对于一个类而言,只有一个引起它变化的原因。例如在SpringMvc中定义一个类UserService,此类中只能负责对用户相关的CRUD操作,而不能将Controller和dao与service混合在一个类中。
遵循这种设计方式能够实现高内聚,低耦合的效果。
3. 里氏代换原则 (Liskov Substitution Principle, LSP)
里式替换原则就是在子类继承父类的时候,如果需要进行功能扩展,不能在子类中改变父类中已经实现的方法,应该通过添加新的方法扩展父类的功能。
4. 依赖倒转原则 (Dependence Inversion Principle, DIP)
依赖倒置原则就是要求面向接口或者面向抽象编程,不能面向实现编程。
5. 接口隔离原则 (Interface Segregation Principle, ISP)
接口隔离就是对于接口来说,应该定义一个或一组相关的方法,即接口的粒度应该更小一些,不能把太多的功能定义在一个接口中。举个例子,比如在一个商城系统中,对于查询功能来说,可能会有商品查询、订单查询、用户信息查询、店铺查询等等,如果把这些查询放在一个接口中,则每一个实现这个接口的类都要实现所有的方法,显然是不合理的。应该按照功能或某种规则将接口的粒度拆的更细一些。
6. 合成复用原则 (Composite Reuse Principle, CRP)
合成复用就是能用组合或者聚合关系,就不用继承,比如Springmvc中的controller中注入service,可以使用service中的方法,而不要使用继承关系。
7. 迪米特法则 (Law of Demeter, LoD)
迪米特法则就是两个不相关的类尽量不要相互引用,比如UserService和ShopService是用户和商品两个不同的Service,不能在UserService中引用ShopService,也不能在ShopService中引用UserService。如果必须使两个类发生联系,可以使用其他类将这两个类关联起来。
三 常用设计模式
常用的设计模式按照功能可分为三种:
创建型:工厂模式、抽象工厂模式、单例模式、原型模式、构建者模式
结构性:适配器模式、装饰模式、代理模式、组合模式、外观模式、桥接模式、享元模式
行为型:模板方法模式、策略模式、观察者模式、中介模式、状态模式、责任链模式、命令模式、迭代器模式、访问者模式、解释器模式。备忘录模式
1. 简单工厂模式(Simple Factory Pattern)
简单工厂模式需要提供一个工厂接口,一个工厂类,工厂类中提供一个工厂方法,可以接受一个参数,根据参数的类型,创建对应的对象。如下图:
以下是一个服装工厂的例子,服装厂可以生产春装和冬装,衣服具有保暖的属性:
创建一个服装工厂接口 Clothing
1 public interface Clothing { 2 //创建一个服装厂接口,服装具“保暖”的属性 3 void keepWarm(); 4 }
创建春装工厂类 SpringClothing实现接口Clothing
1 public class SpringClothing implements Clothing{ 2 3 @Override 4 public void keepWarm() { 5 System.out.println("春装可以保暖。"); 6 } 7 }
创建冬装工厂类 WinterClothing实现接口Clothing
1 public class WinterClothing implements Clothing{ 2 3 @Override 4 public void keepWarm() { 5 System.out.println("冬装可以保暖。"); 6 7 } 8 9 }
创建工厂类 ClothingFactor ,工厂类中有方法 createClothing (String type) 根据传入参数的类型创建相应的对象
1 import simple_factory.product.SpringClothing; 2 import simple_factory.product.WinterClothing; 3 4 public class ClothingFactor { 5 6 public static Object createClothing(String type){ 7 if("SPRING".equals(type)){ 8 return new SpringClothing(); 9 }else if("WINTER".equals(type)){ 10 return new WinterClothing(); 11 }else{ 12 return null; 13 } 14 } 15 }
以上就是简单工厂模式的简单实现,从上述代码不难看出,工厂模式有两个缺点:
(1)如果服装厂想再生产秋装,代码逻辑就需要改动,不利于扩展,违反了开闭原则。
(2)如果生产的产品种类大量增加,工厂类的功能将会太过繁杂。
2. 工厂模式(Factory Pattern)
工厂模式能够解决上述简单工厂的缺点。以上面服装厂为例,简单工厂就是把所有的 “活” 都交给工厂类干,只要需要生产产品,都由工厂类生产。工厂模式和简单工厂的区别是工厂模式对每一种产品都单独建一个工厂,只负责对应产品的生产,这样每一类产品都是一个工厂,所有种类的工厂的总和就是一个大工厂即工厂模式。
工厂模式图如下:
继续以上述服装厂为例:
创建工厂类接口Clothing,SpringClothing,WinterClothing 和简单工厂中一致,再创建工厂类ClothingFactory如下:
1 public abstract class ClothingFactory { 2 3 //工厂方法,此方法可以创建工厂 4 public abstract Clothing createClothing(); 5 }
创建SpringCothingFactory
1 public class SpringCothingFactory extends ClothingFactory{ 2 3 @Override 4 public Clothing createClothing() { 5 return new SpringClothing(); 6 } 7 8 }
创建WinterCothingFactory
1 public class WinterCothingFactory extends ClothingFactory{ 2 3 @Override 4 public Clothing createClothing() { 5 return new WinterClothing(); 6 } 7 8 }
从图中可以看出,每一种产品就对应一个工厂,解决了简单工厂的缺点。
3. 抽象工厂模式(Abstract Factory Pattern)
抽象工厂模式解决了一种产品对应一种工厂,但是往往一个工厂不是生产一种产品,而是生产一系列相关的产品,比如上面上产春装的工厂,生产的产品又可以分为“童装”,“青年装”和“老年装”等多种产品,对于这种业务背景,如果使用工厂模式实现,需要创建更多的工厂,其实在实际环境中,往往一系列具有很多相似特点的产品,可以使用一个共产来生产,即工厂模式。模式图如下:
以上调用图中抽象工厂类 AbstractFactory,生产两类产品 productA和productB,他们各自又细分了1和2两种产品。举个例子,同样以上面服装厂为例,代码结构图如下:
其中,SpringCothing 和 WinterClothing 是春装和冬装两个接口,都有一个共同的生产产品的方法和实现他们的实现类,分别代表了“童装”,“青年装”和“老年装“。 AbstractCothingFactory是抽象工厂类,SpringCothingFactory 和 WinterCothingFactory是继承了AbstractCothingFactory类的工厂类,FactoryProducer是工厂类的生成器类。
代码如下:
SpringCothing :
1 /** 2 * 春装接口 3 */ 4 public interface SpringCothing { 5 /** 6 * 生产服装 7 */ 8 void productionClothing(); 9 }
ChildrenWearSpringCothing:
1 /** 2 * 春装,童装 3 */ 4 public class ChildrenWearSpringCothing implements SpringCothing{ 5 6 /** 7 * 重写方法 8 */ 9 @Override 10 public void productionClothing() { 11 System.out.println("春装,童装"); 12 } 13 }
YouthWearSpringCothing:
1 /** 2 * 青年装,春装 3 */ 4 public class YouthWearSpringCothing implements SpringCothing { 5 6 /** 7 * 重写方法 8 */ 9 @Override 10 public void productionClothing() { 11 System.out.println("青年装,春装"); 12 } 13 }
SeniorWearSpringCothing:
1 /** 2 * 老年装,春装 3 */ 4 public class SeniorWearSpringCothing implements SpringCothing { 5 6 @Override 7 public void productionClothing() { 8 System.out.println("老年装,春装"); 9 } 10 }
WinterClothing:
1 /** 2 * 冬装 3 */ 4 public interface WinterClothing { 5 6 /** 7 * 生产服装 8 */ 9 void productionClothing(); 10 }
ChilderWearWinterCothing:
1 public class ChilderWearWinterCothing implements WinterClothing { 2 @Override 3 public void productionClothing() { 4 System.out.println("冬装,童装"); 5 } 6 }
YouthWearWinterCothing:
1 public class YouthWearWinterCothing implements WinterClothing { 2 @Override 3 public void productionClothing() { 4 System.out.println("冬装,青年装"); 5 } 6 }
SeniorWearWinterCothing:
1 /** 2 * 冬装,老年装 3 */ 4 public class SeniorWearWinterCothing implements WinterClothing { 5 @Override 6 public void productionClothing() { 7 System.out.println("冬装,老年装"); 8 } 9 }
AbstractCothingFactory:
1 /** 2 * 工厂类 3 */ 4 public abstract class AbstractCothingFactory { 5 public abstract SpringCothing getSpringCothing(String springWearType); 6 public abstract WinterClothing getWinterCothing(String winterWearType); 7 }
SpringCothingFactory:
1 ** 2 * 春装工厂类 3 */ 4 public class SpringCothingFactory extends AbstractCothingFactory { 5 6 @Override 7 public SpringCothing getSpringCothing(String springWearType) { 8 if(null == springWearType) { 9 return null; 10 } 11 if("CHILDER".equalsIgnoreCase(springWearType)){ 12 return new ChildrenWearSpringCothing(); 13 } 14 if("YOUTH".equalsIgnoreCase(springWearType)){ 15 return new YouthWearSpringCothing(); 16 } 17 if("SENIOR".equalsIgnoreCase(springWearType)){ 18 return new SeniorWearSpringCothing(); 19 } 20 return null; 21 } 22 23 @Override 24 public WinterClothing getWinterCothing(String winterWearType) { 25 return null; 26 } 27 }
WinterCothingFactory:
1 /** 2 * 冬装工厂类 3 */ 4 public class WinterCothingFactory extends AbstractCothingFactory { 5 6 @Override 7 public SpringCothing getSpringCothing(String springWearType) { 8 return null; 9 } 10 11 @Override 12 public WinterClothing getWinterCothing(String winterWearType) { 13 if(null == winterWearType) { 14 return null; 15 } 16 if("CHILDER".equalsIgnoreCase(winterWearType)){ 17 return new ChilderWearWinterCothing(); 18 } 19 if("YOUTH".equalsIgnoreCase(winterWearType)){ 20 return new YouthWearWinterCothing(); 21 } 22 if("SENIOR".equalsIgnoreCase(winterWearType)){ 23 return new SeniorWearWinterCothing(); 24 } 25 return null; 26 } 27 }
FactoryProducer:
1 /** 2 * 工厂生成器类 3 */ 4 public class FactoryProducer { 5 6 public static AbstractCothingFactory getFactory(String choice){ 7 if(choice.equalsIgnoreCase("SPRING")){ 8 return new SpringCothingFactory(); 9 } else if(choice.equalsIgnoreCase("WINTER")){ 10 return new WinterCothingFactory(); 11 } 12 return null; 13 } 14 }
FactoryMethodDemo:
1 public class FactoryMethodDemo { 2 3 public static void main(String[] args) { 4 //1.获取春装工厂 5 AbstractCothingFactory springCothingFactory = FactoryProducer.getFactory("SPRING"); 6 7 //2.获取“童装,春装”对象 8 SpringCothing childrenWearSpringCothing = 9 springCothingFactory.getSpringCothing("CHILDER"); 10 11 //3.调具体方法,生产“童装,春装”产品 12 childrenWearSpringCothing.productionClothing(); 13 14 //4.获取“青年装,春装”对象 15 SpringCothing youtWearSpringCothing = 16 springCothingFactory.getSpringCothing("YOUTH"); 17 18 //5.调具体方法,生产“青年装,春装”产品 19 youtWearSpringCothing.productionClothing(); 20 21 //======================================================== 22 //1.获取冬装工厂 23 AbstractCothingFactory winterCothingFactory = FactoryProducer.getFactory("WINTER"); 24 25 //2.获取“童装,冬装”对象 26 27 WinterClothing childerWearWinterCothing = 28 winterCothingFactory.getWinterCothing("YOUTH"); 29 //3.生产“童装,冬装” 30 childerWearWinterCothing.productionClothing(); 31 } 32 33 }
运行结果:
抽象工厂不支持在一系列产品中扩展产品的种类,支持对产品系列的扩展。
4. 单例模式(Singleton Pattern)
单例模式属于创建型模式。对于一个单一的类,只能由他自己创建对象并对外提供一个唯一的访问它对象的方式。同时确保只有单个对象被创建。其他类无法实例化它的对象。
单例模式总的来说六种:非线程安全懒汉式、线程安全懒汉式 、饿汉式、双重检测锁、静态内部类和枚举。其中除了非线程安全懒汉式,其他都是多线程安全的。
直接以代码为例:
(1)非线程安全懒汉式
1 /** 2 * 懒汉式,线程不安全 3 */ 4 public class SingletonLazy { 5 6 private static SingletonLazy singletonLazy; 7 8 private SingletonLazy(){} 9 10 public static SingletonLazy getSingletonLazy(){ 11 if(null == singletonLazy){ 12 singletonLazy = new SingletonLazy(); 13 } 14 return singletonLazy; 15 } 16 17 public void method(){ 18 System.out.println("非线程安全懒汉:这是单例类的一个普通方法"); 19 } 20 }
保证只有单个对象被创建是第8行代码的私有构造器,即其他类不能对此类实例化,只能通过getSingletonLazy()方法实例化对象。
(2)线程安全懒汉
1 /** 2 * 线程安全懒汉 3 */ 4 public class SingletonLazyThreadSafe { 5 6 private static SingletonLazyThreadSafe singletonLazyThreadSafe; 7 8 private SingletonLazyThreadSafe(){} 9 10 public static synchronized SingletonLazyThreadSafe getSingletonLazyThreadSafe(){ 11 if(null == singletonLazyThreadSafe){ 12 singletonLazyThreadSafe = new SingletonLazyThreadSafe(); 13 } 14 return singletonLazyThreadSafe; 15 } 16 17 public void method(){ 18 System.out.println("线程安全懒汉:这是单例类的一个普通方法"); 19 } 20 }
线程安全懒汉式和非线程安全懒汉式的区别时getSingletonLazyThreadSafe()方法增加了synchronized关键字。保证多线程下,线程能够同步访问此方法实例化对象。可延时加载。
(3)线程安全饿汉式
1 /** 2 * 饿汉式 3 */ 4 public class Singleton { 5 6 private static Singleton singleton = new Singleton(); 7 8 private Singleton(){} 9 10 public static Singleton getSingleton(){ 11 return singleton; 12 } 13 14 public void method(){ 15 System.out.println("线程安全饿汉式:这是单例类的一个普通方法"); 16 } 17 }
饿汉式线程安全,调用效率高,但是不能延时加载。
(4)双重检测锁
1 /** 2 * 双重检测锁实现单例模式 3 */ 4 public class SingletonDouLock { 5 6 private volatile static SingletonDouLock singletonDouLock; 7 8 private SingletonDouLock(){} 9 10 public static SingletonDouLock getSingletonDouLock(){ 11 if(null == singletonDouLock){ 12 synchronized (SingletonDouLock.class){ 13 if(null == singletonDouLock){ 14 singletonDouLock = new SingletonDouLock(); 15 } 16 } 17 } 18 return singletonDouLock; 19 } 20 21 public void method(){ 22 System.out.println("双重检测锁:这是单例类的一个普通方法"); 23 } 24 }
双重检测锁是一个比较经典的算法,即代码的10~19行,在很多的源码中都有使用。第6行代码保证了,在类加载的时候,就初始化了对象,由类加载机制实现了线程同步,保证了多线程下的线程安全。
(5)静态内部类
1 /** 2 * 静态内部类实现单例模式 3 */ 4 public class SingletonStaticInnerClass { 5 6 /** 7 * 静态内部类 InnerClass 8 * 静态内部类的一个特点是:不能从静态内部类的对象中访问非静态外部类的对象 9 */ 10 private static class InnerClass{ 11 private static final SingletonStaticInnerClass singletonStaticInnerClass = 12 new SingletonStaticInnerClass(); 13 } 14 15 /** 16 * 私有构造方法 17 */ 18 private SingletonStaticInnerClass(){} 19 20 public static final SingletonStaticInnerClass getSingletonStaticInnerClass(){ 21 return InnerClass.singletonStaticInnerClass; 22 } 23 24 public void method(){ 25 System.out.println("静态内部类:这是单例类的一个普通方法"); 26 } 27 28 }
关于静态内部类的详细说明请参考:https://www.cnblogs.com/xyzyj/p/6139421.html
静态内部类的一个重要特性:不能从静态内部类的对象中访问非静态外部类的对象。
(6)枚举
1 /** 2 * 枚举 3 */ 4 public enum SingletonEnum { 5 6 INSTANCE; 7 8 public void method(){ 9 System.out.println("枚举:这是单例类的一个普通方法"); 10 } 11 }
主方法:
1 public static void main(String [] args){ 2 //1.懒汉式,线程不安全 3 SingletonLazy singletonLazy = SingletonLazy.getSingletonLazy(); 4 singletonLazy.method(); 5 6 //2.懒汉式,线程安全 7 SingletonLazyThreadSafe singletonLazyThreadSafe = 8 SingletonLazyThreadSafe.getSingletonLazyThreadSafe(); 9 singletonLazyThreadSafe.method(); 10 11 //3.线程安全饿汉式 12 Singleton singleton = Singleton.getSingleton(); 13 singleton.method(); 14 15 //4.双重检测锁 16 SingletonDouLock singletonDouLock = SingletonDouLock.getSingletonDouLock(); 17 singletonDouLock.method(); 18 19 //静态内部类 20 SingletonStaticInnerClass singletonStaticInnerClass = SingletonStaticInnerClass.getSingletonStaticInnerClass(); 21 singletonStaticInnerClass.method(); 22 23 //枚举 24 SingletonEnum.INSTANCE.method(); 25 26 }
执行结果:
5. 原型模式(Prototype Pattern)
原型模式是创建型的设计模式。它的主要思想说的是“对象的复制”。
在实际开发过程中,很多情况下,需要将一个已经处理了一部分的对象拷贝一份。拷贝对象的方法有很多种,原型模式说的就是Object类的clone()拷贝方法。
要使用Object类的clone()方法,就需要实现Cloneable接口,下面先以jdk1.8中的Vector类来研究一下。
查看Vector类:
在源码中,Vector类实现了Cloneable接口;再看Vector类中的clone方法如下:
上图中的 Vector<E> v = (Vector<E>) super.clone(); 就是调用Object类的clone方法
写一个测试类:
1 package prototype_pattern; 2 3 import java.util.Vector; 4 5 public class Main { 6 public static void main(String [] args){ 7 Vector<String> vector = new Vector<>(); 8 vector.add("张三"); 9 System.out.println(vector.toString()); 10 11 Vector clone = (Vector)vector.clone(); 12 System.out.println(clone.toString()); 13 14 System.out.println("######################"); 15 16 clone.add("李四"); 17 System.out.println(vector.toString()); 18 System.out.println(clone.toString()); 19 } 20 }
上面的main方法中,首先创建了一个vector对象,再通过vector对象调clone方法克隆出一个对象 clone 。
debug跟下代码如下:
从上图可以看出,两个对象地址是完全不同的,也就是说两个对象是独立的对象。
再看程序输出结果:也说明两个对象是完全不同的两个对象。
原型模式拷贝对象属于浅拷贝,即如果对象被拷贝的对象内存在引用,引用是不会被拷贝,要实现拷贝,需引用对象再拷贝。
原型模式的好处是:减少频繁创建对象对资源的消耗。创建对象的过程实际是很复杂的,通过一系列的加载验证解析等操作,最终在堆中创建对象。而原型模式的对象复制只是把堆中的对象再在堆中复制一份,减少了资源的消耗。
6. 构建者模式(Builder Pattern)
构建者模式指的是将一个复杂对象的构建过程与它的表示分离。
1.业务背景:
笔者在生活中遇到这样一个问题。闲来无事的时候,喜欢总结一下最近的消费记录,这样能更好的规划下一段时间的消费计划。但是,手机上有众多支付app
,比如支付宝、微信。云闪付等等。如果要查看消费账单,得一个一个查,查着查着就乱了。于是,笔者就想,能不能做一款app,和各个支付厂商合作,对接其支付app,把所有app的账单按照一定规则查出来。用户只需要打开一个查询账单app,最近的消费账单就一目了然。各个app查询账单的不同情况组合就是构建过程,最终通过查询app展示的账单就是表示。
当然,这个例子在现实生活中可能不太现实,但是对于说明构建者模式很是明了。
以上面的例子为例,使用构建者模式实现。主要有两部分:产品部分和构建部分。有人可能会说有五部分,这只是看的角度不同,以及分的粒度不同。
现以支付宝和微信两个app为例。
2.功能描述:
通过账单查询app对接支付宝和微信,查询支付宝和微信的账单。
支付宝和微信都可单独查询“最近一周账单”、“最近一个月账单”和“最近一年账单”。如下表:
如上图,查询情况可以任意多条,任意情况组合,而并不是两两组合。比如,查出微信最近一周、一月、一年和支付宝最近一月账单。并显示系统名称和每一次查询的总账单金额。
按照如下图格式输出到控制台:
上图是三种不同的具体查询结果。
3.功能分析
对于以上需求,如果不使用构建者模式实现,可以想象其复杂程度,可能会有成千上百个 if ...else....。而且,现在只有两个app,如果手机上有多个支付app,查询情况有多种,那么,if..else...根本无法实现。
基于以上功能,使用构建者模式实现,可以做到不用一个if...else...。先看整体结构图:
如上图,先看个大概,主要两部分,大框里的是产品部分,也就是功能中具体的六种查询情况,小框中是组建者,是根据用户不同具体的查询要求组建组建查询功能。其他都是辅助或者测试类。
4.代码实现
① 账单列表接口
1 public interface Bill { 2 3 /** 4 * 系统 5 * @return 6 */ 7 BillSystem billSystemName(); 8 9 /** 10 * 时间 11 * @return 12 */ 13 String time(); 14 15 /** 16 * 金额 17 * @return 18 */ 19 float money(); 20 }
② 微信抽象类
1 public abstract class WX implements Bill { 2 3 @Override 4 public BillSystem billSystemName() { 5 return new System_WX_Abstract(); 6 } 7 }
③微信最近一周账单
1 public class WX_Week extends WX { 2 3 @Override 4 public String time() { 5 return "最近一周账单:"; 6 } 7 8 @Override 9 public float money() { 10 return 500; 11 } 12 }
④ 微信最近一个月消费账单
1 public class WX_Month extends WX { 2 3 @Override 4 public String time() { 5 return "最近一个月账单:"; 6 } 7 8 @Override 9 public float money() { 10 return 2000; 11 } 12 }
⑤ 微信最近一年账单
1 public class WX_Year extends WX { 2 3 @Override 4 public String time() { 5 return "最近一年账单:"; 6 } 7 8 @Override 9 public float money() { 10 //此处应调相应接口获取,这里写死 11 return 30000; 12 } 13 }
⑥ 支付宝抽象类
1 public abstract class ZFB implements Bill{ 2 3 @Override 4 public BillSystem billSystemName() { 5 return new System_ZFB_Abstract(); 6 } 7 }
⑦ 支付宝最近一周账单
1 public class ZFB_Week extends ZFB { 2 3 @Override 4 public String time() { 5 return "最近一周账单:"; 6 } 7 8 @Override 9 public float money() { 10 return 300; 11 } 12 }
⑧ 支付宝最近一月账单
1 public class ZFB_Month extends ZFB { 2 3 @Override 4 public String time() { 5 return "最近一月账单:"; 6 } 7 8 @Override 9 public float money() { 10 return 3000; 11 } 12 }
⑨ 支付宝最近一年账单
1 public class ZFB_Year extends ZFB{ 2 @Override 3 public String time() { 4 return "最近一年账单:"; 5 } 6 7 @Override 8 public float money() { 9 return 60000; 10 } 11 }
⑩ 微信系统实现类
1 public class System_WX_Abstract implements BillSystem { 2 3 @Override 4 public String billSystemName() { 5 return "微信"; 6 } 7 }
⑪ 支付宝系统实现类
1 public class System_ZFB_Abstract implements BillSystem{ 2 3 @Override 4 public String billSystemName() { 5 return "支付宝"; 6 } 7 }
⑫ 系统接口
1 public interface BillSystem { 2 /** 3 * 系统 4 * @return 5 */ 6 String billSystemName(); 7 }
⑬ 构建复杂情况类
1 public class BillBuilder { 2 3 /** 4 * 微信最近一周账单 5 * 支付宝最近一个月账单 6 * @return 7 */ 8 public Item test1 (){ 9 Item item = new Item(); 10 item.addItem(new WX_Week()); 11 item.addItem(new ZFB_Month()); 12 return item; 13 } 14 15 /** 16 * 微信最近一年账单 17 * 支付宝最近一周账单 18 * @return 19 */ 20 public Item test2 (){ 21 Item item = new Item(); 22 item.addItem(new WX_Year()); 23 item.addItem(new ZFB_Week()); 24 return item; 25 } 26 27 28 /** 29 * 支付宝最近一周账单 30 * 支付宝最近一月账单 31 * @return 32 */ 33 public Item test3 (){ 34 Item item = new Item(); 35 item.addItem(new ZFB_Week()); 36 item.addItem(new ZFB_Month()); 37 return item; 38 } 39 }
⑭测试
1 public class Main { 2 3 public static void main(String [] args){ 4 5 BillBuilder billBuilder = new BillBuilder(); 6 Item item1 = billBuilder.test1(); 7 item1.showItems(); 8 9 Item item2 = billBuilder.test2(); 10 item2.showItems(); 11 12 Item item3 = billBuilder.test3(); 13 item3.showItems(); 14 } 15 }
运行结果:
从上述例子可看出,构建者模式将“账单”的具体构建过程和最终的表现分离。如果需要再增加一个支付软件的查询功能,也是很容易扩展,但缺点是内部构建的这些功能必须有相似的地方,比如上述例子中的账单查询对于支付宝和微信是相似的功能,如果要扩展一个下单功能,就无法实现。
7. 适配器模式(Adapter Pattern)
将一个类的接口转换成客户希望的另外一个接口。即对于一个新的功能,在不改变原有的接口的情况下,实现新的功能。在实际业务场景中,这是非常常见的一种情况。比如购物商城体统,原来有一个订单查询接口,只能查询近一个月订单,随着业务的扩展,需要查询近一年的订单,又不能改变原来代码,此时可使用适配器模式。
现实生活中也有很多例子,如,新买了一个手机,却没有以前那种圆形的耳机孔,而以前花高价买的圆形耳机孔又舍不得丢弃。于是可以买一个适配器如下图:
适配器模式的核心思想其实就是新增一个接口,在新的接口中调原接口的放方法,并且新增新功能的方法。
适配器模式有三种:
- 类适配器
- 对象适配器
- 接口适配器
以上面耳机孔为例举例子,现有一部新手机,只提供了 Type-C 插口,需要将此插孔转换为 3.5mm的圆孔耳机插孔。
现有类 TypeC 如下:
1 /** 2 * 新买的手机,原接口(已有接口) 3 * 只提供type-c 插孔 4 */ 5 public class TypeC { 6 7 public void type_C() { 8 System.out.println("提供type_C插孔"); 9 } 10 }
需要转换的3.5mm接口如下:
1 /** 2 * 目标类 3 * 提供3.5mm的圆孔插孔 4 */ 5 public interface Y35 { 6 7 /** 8 * 提供3.5mm的圆孔插孔 9 */ 10 void provideY35(); 11 }
以下就用类适配器和对象适配器将Type-C转换成为3.5mm的插孔。
1.类适配器:
创建类 TyptCToY35Adapter
1 /** 2 * 在某平台买的转换器,实现Y_3_5接口 3 */ 4 public class TyptCToY35Adapter extends TypeC implements Y35 { 5 6 /** 7 * 提供3.5mm的圆孔插孔 8 */ 9 @Override 10 public void provideY35() { 11 this.type_C(); 12 System.out.println("类转换器:使用转换器转换"); 13 } 14 }
测试:
1 public class Main { 2 3 public static void main(String [] args){ 4 Y35 y35 = new TyptCToY35Adapter(); 5 y35.provideY35(); 6 System.out.println("正常使用3.5mm插孔"); 7 } 8 }
空值台打印:
2.对象适配器:
创建类TyptCToY35Adapter
1 public class TyptCToY35Adapter implements Y35 { 2 3 private TypeC typeC; 4 5 public TyptCToY35Adapter(TypeC typeC){ 6 this.typeC = typeC; 7 } 8 9 @Override 10 public void provideY35(){ 11 typeC.type_C(); 12 System.out.println("对象转换器:使用转换器转换"); 13 } 14 }
测试:
1 public class Main { 2 3 public static void main(String [] args){ 4 TypeC typeC = new TypeC(); 5 Y35 y35 = new TyptCToY35Adapter(typeC); 6 y35.provideY35(); 7 System.out.println("正常使用3.5mm插孔"); 8 } 9 }
控制台打印:
从上述代码可以看出,适配器模式就是在新的接口中调原接口的逻辑,并加入新功能的逻辑。类适配器和对象适配器所不同的是类适配器通过继承原类来调用其方法,对象适配器是通过实例化原类的对象来调其方法。
2.接口适配器:
接口适配器和类适配器以及对象适配器不太一样,它主要的作用是对于一个具有多个方法的接口选择性的实现。在实际业务场景中也是如此,一个接口定义了很多方法,但并不是每一个子类都需要这些方法,则可能通过抽象类实现接口,再具体的子类继承抽象类重写需要的方法即可。比如一个购物商城系统的一个接口中,定义了下单方法,查询方法和支付方法等(实际业务场景中不会这么定义,此处只为了说明接口适配器),对于支付子类就不需要实现下单方法,代码如下:
Shopping
1 public interface Shopping { 2 3 /** 4 * 下单 5 */ 6 void order(); 7 8 /** 9 * 查询 10 * @return 11 */ 12 String query(); 13 14 /** 15 * 支付 16 */ 17 void pay(); 18 }
抽象类Pay:
1 public abstract class Pay implements Shopping { 2 @Override 3 public void pay() { 4 System.out.println("支付方法"); 5 } 6 7 @Override 8 public void order() { 9 //什么也不用写 10 } 11 12 @Override 13 public String query() { 14 //什么也不用写 15 return null; 16 } 17 }
具体的支付类:vip会员支付类,继承抽象类
1 public class VipPay extends Pay { 2 @Override 3 public void pay() { 4 super.pay(); 5 } 6 }
测试:
public class Main { public static void main(String [] args){ VipPay vipPay = new VipPay(); vipPay.pay(); } }
输出:
8. 装饰模式(Decorator Pattern)
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者模式比较简单,就是不改变原来接口或者抽象类的情况下,对已有的对象动态的增加一些属性。而不是使用继承来增加,使接口的扩展更加灵活。
生活中的例子:房子装修,房子不变,选择性的给房子装饰一些涂料等;去买笔记本,一个笔记本的价格不变,可以再增加一个鼠标,或者一个键盘,或者一个电脑包,或者任意选择几种配件,最终结果是,笔记本原价格不变的情况下,增加了配件的价格,还有外卖套餐等很多例子。
就以笔记本例子用代码实现。
装饰者模式有四个角色:
- Component(抽象构建):笔记本抽象接口;
- ConcreteComponent(具体构建):这里只笔记本名称,比如华为笔记本,thinkPad笔记本等;
- Decorator(装饰器):表示具体配件的抽象类或者普通父类,实现笔记本接口;
- ConcreteDecorator(具体装饰器):表示笔记本的一些具体配件,比如键盘,鼠标,电脑包等。
购买笔记本的选择性很,假如现在只有华为笔记本和ThinkPad笔记本两种,配件只有键盘,鼠标,电脑包,并且这五种商品价格都是固定的,买家选择可能性如下:
如上图,如果用户只买一种笔记本,配件可选,可不选那用户的选择性用排列组合公式 计算,就有:( C(1,3) + C(2,3) +C(3,3) + 1 + 1 ) * 2 = 16 种。
对于以上情况,不使用装饰者模式的情况下代码是比较复杂的,类非常多。这里不讨论怎么实现。使用装饰者模式实现只需要7个类即可,具体的类结构图如下:
如上图,图中有四个框代表装饰者模式的四个组成部分。
具体代码如下:
① 笔记本接口
1 ** 2 * 笔记本抽象类 3 * 笔记本有两个特性:名称,价格 4 */ 5 public interface Notebook { 6 7 /** 8 * 名称 9 * @return 10 */ 11 String name(); 12 13 /** 14 * 价格 15 * @return 16 */ 17 float price(); 18 19 }
②具体笔记本ThinkPad
1 /** 2 * ThinkPad 笔记本 3 */ 4 public class ThinkPad implements Notebook { 5 6 @Override 7 public String name() { 8 return "ThinkPad笔记本裸机"; 9 } 10 11 /** 12 * 裸机价格 5000 13 * @return 14 */ 15 @Override 16 public float price() { 17 return 5000; 18 } 19 }
③ 具体笔记本华为
1 /** 2 * 华为笔记本 3 */ 4 public class HuaWei implements Notebook { 5 6 @Override 7 public String name() { 8 return "华为笔记本裸机"; 9 } 10 11 /** 12 * 裸机价格 6000 13 * @return 14 */ 15 @Override 16 public float price() { 17 return 6000; 18 } 19 }
④ 配件抽象类
1 /** 2 * 笔记本配件抽象类 3 * 起到承上启下作用,给具体的子类配件定义笔记本原本的特性,即品牌名称和价格 4 */ 5 public class Accessories implements Notebook { 6 7 @Override 8 public String name() { 9 return null; 10 } 11 12 @Override 13 public float price() { 14 return 0; 15 } 16 }
⑤ 具体配件电脑包
1 ** 2 * 具体配件,电脑包 3 * 有两个特性,名称和价格 4 */ 5 public class Package extends Accessories{ 6 7 /** 8 * 名称:电脑包,价格:80 9 */ 10 private static final String MOUSE_NAME = "电脑包"; 11 private static final float MOUSE_PRICE = 80; 12 13 /** 14 * 通过构造方法,将已经选好的套餐传进来,即将notebook对象传进来 15 * 并在notebook对象的基础上增加此类所表示的对象的特性(名称,价格) 16 * @param notebook 17 */ 18 Notebook notebook = null; 19 20 public Package(Notebook notebook){ 21 this.notebook = notebook; 22 } 23 24 @Override 25 public String name() { 26 return notebook.name() +"加一个"+ MOUSE_NAME; 27 } 28 29 @Override 30 public float price() { 31 return notebook.price() + MOUSE_PRICE; 32 } 33 }
⑥ 具体配件鼠标
1 ** 2 * 具体配件,鼠标 3 * 有两个特性,名称和价格 4 */ 5 public class Mouse extends Accessories{ 6 7 /** 8 * 名称:鼠标,价格:200 9 */ 10 private static final String MOUSE_NAME = "鼠标"; 11 private static final float MOUSE_PRICE = 200; 12 13 Notebook notebook = null; 14 public Mouse(Notebook notebook){ 15 this.notebook = notebook; 16 } 17 18 @Override 19 public String name() { 20 return notebook.name() +"加一个"+ MOUSE_NAME; 21 } 22 23 @Override 24 public float price() { 25 return notebook.price() + MOUSE_PRICE; 26 } 27 }
⑦ 具体配件键盘
1 ** 2 * 具体配件,键盘 3 * 有两个特性,名称和价格 4 */ 5 public class Keyboard extends Accessories{ 6 7 /** 8 * 名称:键盘,价格:100 9 */ 10 private static final String KEY_BOARD_NAME = "键盘"; 11 private static final float KEY_BOARD_PRICE = 100; 12 13 Notebook notebook = null; 14 public Keyboard(Notebook notebook){ 15 this.notebook = notebook; 16 } 17 @Override 18 public String name() { 19 return notebook.name() +"加一个"+ KEY_BOARD_NAME; 20 } 21 22 @Override 23 public float price() { 24 return notebook.price() + KEY_BOARD_PRICE; 25 } 26 }
测试:
1 public class Main { 2 public static void main(String [] args){ 3 4 /** 5 * 买ThinkPad 6 */ 7 Notebook notebook = new ThinkPad(); 8 System.out.println(notebook.name()+"价格:"+notebook.price()+"元;"); 9 //加一个键盘 10 notebook = new Keyboard(notebook); 11 System.out.println(notebook.name()+"价格:"+notebook.price()+"元;"); 12 //再加一个电脑包 13 notebook = new Package(notebook); 14 System.out.println(notebook.name()+"价格:"+notebook.price()+"元;"); 15 System.out.println("=========================================="); 16 /** 17 * 买华为 18 */ 19 Notebook notebook1 = new HuaWei(); 20 System.out.println(notebook1.name()+"价格:"+notebook1.price()+"元;"); 21 //加一个电脑包 22 notebook1 = new Package(notebook1); 23 System.out.println(notebook1.name()+"价格:"+notebook1.price()+"元;"); 24 //再加一个鼠标 25 notebook1 = new Mouse(notebook1); 26 System.out.println(notebook1.name()+"价格:"+notebook1.price()+"元;"); 27 } 28 }
控制台输出:
总结:装饰者模式的核心思想就是在原有的基础上增加,和适配器模式的区别时,适配器模式是屏蔽原有接口,在新的接口中调原接口的中方法。
以上例子属于一种透明的装饰模式,还有半透明的装饰者模式,以上面例子说明,如果再增加一个配件,它不仅有名称和价格两个属性,它还增加了其他属性,而原笔记本接口中只有两个属性,这种情况就是半透明装饰着模式。
jdk1.8中的 InputStream 就使用了装饰者模式,如下图:
其中 BufferedInputStream 就相当于上述例子中的具体配件。
9. 代理模式(Proxy Pattern)
代理模式是一个类代表另一个类的功能。这是最简单的设计模式。在实际业务场景中,有时直接使用类去创建对象可能会带来一些问题,或者由于系统限制,不能使用原有类创建对象。
现实中的代理情况比较多,比如各种中介机构、各种代理人、以及程序员在家办公远程公司电脑时使用代理。
下面以租房为例。房东可以将房子直接租给租户,也可以将房子租赁权限给中介机构,中介机构再租赁给租户。
代理模式由三部分组成:
- 抽象类或者接口
- 继承抽象类或实现接口的具体类(被代理类)
- 代理类(也需实现接口)
代码如下:
① 创建接口
1 /** 2 * 租房接口 3 */ 4 public interface Rent { 5 6 /** 7 * 租房方法 8 */ 9 void rent(); 10 }
② 创建实现类
1 /** 2 * 业主直租 3 */ 4 public class Owner implements Rent { 5 6 @Override 7 public void rent() { 8 System.out.println("房子出租完成"); 9 } 10 }
③创建代理类
1 /** 2 * 中介代码 3 */ 4 public class ProxyIntermediary implements Rent{ 5 6 private Owner owner = null; 7 8 @Override 9 public void rent() { 10 if(owner == null){ 11 owner = new Owner(); 12 } 13 System.out.println("中介代理开始"); 14 owner.rent(); 15 } 16 }
测试
1 public class Main { 2 public static void main(String [] args) { 3 Owner owner = new Owner(); 4 System.out.println("房东直租"); 5 owner.rent(); 6 System.out.println("========================"); 7 ProxyIntermediary proxyIntermediary = new ProxyIntermediary(); 8 proxyIntermediary.rent(); 9 } 10 }
控制台打印
从上述代理可看出,代理模式说白了,其实就是在代理类中创建被代理对象,然后使用代理对象可以访问被代理类的方法。
10. 模板方法模式(Template Pattern)
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
如何理解上述概念,其实就是给一个模板,在模板不变的前提下,增加其他功能。放到代码里,就是对于一个抽象类,定义了一些公共的且不变的方法和一些可变的方法,可变的方法在子类中实现,这样就可以做到子类只需要重写需要变的方法,不变的方法公共方法会自动给子类加上。
显示生活中的例子:毕业论文,都有一个固定的模板,学生在模板不变的情况下,根据自己的毕业设计完成毕业论文,最终的论文结果是,既包含模板部分,又包含学生自己独有的部分,对于每一个学生的论文来说,都包含了公共的模板部分。
好处,子类不用重复的实现公共部分。正如论文,老师如果只给学生口头讲一下论文骨架,每个学生都得按照老师的要求写一遍,如果老师自己写一套模板,再把模板发给学生,每个学生都不用写公共部分了,只需要写自己独有的部分。
程序员在公司按照需求写代码之前需要写一个设计文档。每个公司的设计文档都有一套模板,这个模板对每一个需求都适用,而需求都是不一样的,以此为例,用代码实现。
模板规则:设计文档必须有“保密条款”,“文档说明”两部分,这两部分是不变的,其次就是正文,每个需求的正文是不一样的。假设一个购物系统有“订单需求”和“支付需求”两个需求需要写设计文档,代码如下。
①创建抽象类
1 /** 2 * 设计文档抽象类 3 */ 4 public abstract class DesignDocument { 5 6 /** 7 * 模板方法,final修饰,不能被重写 8 */ 9 final void templateMethod(){ 10 privacyPolicy(); 11 documentDescription(); 12 text(); 13 } 14 15 void privacyPolicy(){ 16 System.out.println("保密条款,此文档只能内部使用。。。"); 17 } 18 19 void documentDescription(){ 20 System.out.println("创建文档第一版,版本号。。。"); 21 } 22 23 /** 24 * 正文,具体的子类实现 25 */ 26 abstract void text(); 27 }
② 订单需求类
1 /** 2 * 订单需求设计文档 3 */ 4 public class OrderDemand extends DesignDocument{ 5 6 @Override 7 void text() { 8 System.out.println("订单需求的正文。。。"); 9 } 10 }
③ 支付需求类
1 public class PaymentDemand extends DesignDocument { 2 3 @Override 4 void text() { 5 System.out.println("支付需求的正文"); 6 } 7 }
测试:
1 public class Main { 2 public static void main(String [] args){ 3 DesignDocument designDocument = new OrderDemand(); 4 designDocument.templateMethod(); 5 System.out.println("========================"); 6 DesignDocument designDocument1 = new PaymentDemand(); 7 designDocument1.templateMethod(); 8 } 9 }
控制台输出:
从上述可看出,模板模式定义了具体的模板,简化了子类的工作,将公共的功能在抽象父类中实现。
11. 策略模式(Strategy Pattern)
策略模式属于属于行为型模式。它主要是解决多重 if(){}else{}这种情况的,其实就是把每一个判断条件中的处理代码封装成一个类。比如博主在做公司项目时,经常会根据省代码或者市代码做不同的处理,具体业务规则不在此描述,大概代码如下:
if("BEI_JING".equals(province)){ }else if("HE_BEI".equals(province)){ }else{ }
如果,要对中国所有省分分别做处理,那就得写三十几个if...else 。因此,需要将每个省的处理逻辑单独放在一个类中。策略模式就是基于此思想实现的。以上面的例举例,逻辑图如下:
如上图,其中Strategy 是接口,定义了一个处理逻辑的方法,Beijing等是实现此接口的实现类,做具体的逻辑处理,Context是起着承上启下的作用,即上下文类。代码如下;
Strategy:
1 /** 2 * 策略模式 3 * 定义接口,接口中的方法就是对各个条件下需要实现的方法的共性的抽象 4 */ 5 public interface Strategy { 6 7 void doSomthing(); 8 9 }
Guangdong:
1 /** 2 * 广东 3 */ 4 public class Guangdong implements Strategy { 5 @Override 6 public void doSomthing() { 7 System.out.println("广东逻辑"); 8 } 9 }
Hebei:
1 /** 2 * 河北 3 */ 4 public class Hebei implements Strategy{ 5 @Override 6 public void doSomthing() { 7 System.out.println("河北逻辑"); 8 } 9 }
BeiJing
1 /** 2 * 北京 3 */ 4 public class Beijing implements Strategy { 5 6 @Override 7 public void doSomthing() { 8 System.out.println("北京逻辑"); 9 } 10 }
Context:
1 public class Context { 2 private final Strategy strategy; 3 4 public Context(Strategy strategy){ 5 this.strategy = strategy; 6 } 7 8 public void doSomthing(){ 9 this.strategy.doSomthing(); 10 } 11 }
测试:
1 public static void main(String [] agrs){ 2 Context context = new Context(new Beijing()); 3 context.doSomthing(); 4 5 Context context2 = new Context(new Hebei()); 6 context2.doSomthing(); 7 }
结果:
12. 责任链模式(Chain of Responsibility Pattern)
责任链模式,为请求创建了一个接收者对象的链。这种模式给处理者传入请求的类型,各个处理者在这条链上对请求处理,不需要请求者逐个去和每个处理者发生联系。好处:发送者和接收者进行解耦。
生活最常见的例子就是开具各种证明,比如某村张某需要建造一个养殖场,需要向各级政府申请,于是张某拿着申请书去村委会盖章,再拿着申请书去乡镇政府申请盖章,最后再拿着申请书去县委申请盖章,对于张某来说,无疑是非常痛苦的。如果上下级达成一致,张某只需要将申请书交给村委,村委处理后再由村委交递给乡镇,然后乡镇处理后再由乡镇递交给县委处理,这样对于张某来说,就轻松多了。更重要的是在前者情况下,如果村里有几十个人要申请项目,大家都跑来跑去跑乱了。
再一个相似的例子就是公司的各种申请流程,比如离职申请,加薪申请流程,请假申请流程等。
下面以一个权限管理的例子来详细说明:
假设要做一个学习视频的系统,系统中有三种视频课程:免费课程、付费小课、付费大课。用户有三种等级:普通用户权限、vip用户权限、超级vip用户权限。为了描述清楚,下面将课程类别称为角色。权限对应关系如下表:
现在需要实现一个功能:当一个用户登录系统后,需要显示用户可以学习哪些课程。
分析:
如果按照普通的方法实现,即将用户对象出传给每一个角色对象,由具体的对象处理。这样会使对象的耦合性比较强。这里不再说明实现代码。
第二种就是责任链模式,核心思想就是上面张某申请书的第二种实现,责任链模式的构成如下图:
如上图:Handler是所有处理类的抽象父类,其子类就是具体的角色类,也就是此例中的课程类别。用大白话说,各个角色就像一条铁链上的一个一个环,handler就像把这些环连起来的线。User是具体的用户对象。实现代码如下:
①创建用户接口
1 public interface IUser { 2 /** 3 * 用户名称 4 * @return 5 */ 6 String userName(); 7 8 /** 9 * 用户积分 10 * @return 11 */ 12 int score(); 13 }
②创建用户对象
1 public class User implements IUser{ 2 3 String userName; 4 int score; 5 6 public User(String userName,int score){ 7 this.userName = userName; 8 this.score = score; 9 } 10 11 @Override 12 public String userName() { 13 return userName; 14 } 15 16 @Override 17 public int score() { 18 return score; 19 } 20 }
③创建处理器抽象类
1 /** 2 * 抽象处理类 3 */ 4 public abstract class Handler { 5 6 /** 7 * 会员权限等级划分界限 8 */ 9 public static final int MARK_ZORO = 0; 10 public static final int MARK_MIN = 100; 11 public static final int MARK_MID = 500; 12 public static final int MARK_MAX = 1000; 13 14 private int numStart = 0; 15 private int numEnd = 0; 16 /** 17 * 这个属性在具体角色中使用,代表当前角色的下一个处理角色 18 */ 19 private Handler nextHandler; 20 21 public void setNextHandler(Handler nextHandler) { 22 this.nextHandler = nextHandler; 23 } 24 25 public Handler getNextHandler() { 26 return nextHandler; 27 } 28 29 /** 30 * 此构造方法为了让角色赋值 31 * @param numStart 32 * @param numEnd 33 */ 34 public Handler(int numStart, int numEnd) { 35 this.numStart = numStart; 36 this.numEnd = numEnd; 37 } 38 39 /** 40 * 判断方法 41 * 进来一个用户,判断该用户可以学习哪些课程 42 */ 43 public final void judge(IUser user){ 44 /**用当前用户(this代表的角色)的积分依次和各个角色的要求对比, 45 * 如果在该角色的处理范围内,则调此角色的提示方法 46 */ 47 if(user.score() > this.numStart){ 48 this.out(); 49 //如果当前角色还有下一个角色,并且用户积分比当前角色能处理的范围大,则让一下角色处理 50 if(null != this.getNextHandler() && user.score() > this.numEnd){ 51 this.getNextHandler().judge(user); 52 } 53 } 54 } 55 56 /** 57 * 抽象方法,子类实现,每个子类的具体处理逻辑,在这只是给用户提示 58 */ 59 public abstract void out(); 60 }
④创建普通用户权限类
1 /** 2 * 普通用户权限 3 */ 4 public class GeneralAuthority extends Handler{ 5 6 /** 7 * 调父类构造方法给用户赋予权限 8 * 积分在0~100之间的为普通用户 9 */ 10 public GeneralAuthority(){ 11 super(Handler.MARK_ZORO,Handler.MARK_MIN); 12 } 13 14 @Override 15 public void out() { 16 System.out.println("您可以学习免费课程!"); 17 } 18 }
⑤创建VIP用户权限类
1 /** 2 * vip用户权限 3 */ 4 public class VipAuthority extends Handler{ 5 6 /** 7 * 调父类构造方法给用户赋予权限 8 * 积分在100~500之间的为VIP用户 9 */ 10 public VipAuthority(){ 11 super(Handler.MARK_MIN,Handler.MARK_MID); 12 } 13 14 @Override 15 public void out() { 16 System.out.println("您可以学习付费小课!"); 17 } 18 }
⑦创建超级vip权限类
1 /** 2 * 超级会员权限 3 */ 4 public class SuperVipAuthority extends Handler{ 5 6 /** 7 * 调父类构造方法给用户赋予权限 8 * 积分在500~1000之间的为超级VIP用户 9 */ 10 public SuperVipAuthority(){ 11 super(Handler.MARK_MID,Handler.MARK_MAX); 12 } 13 14 @Override 15 public void out() { 16 System.out.println("您可以学习付费大课!"); 17 } 18 }
测试:
1 public class Main { 2 public static void main(String [] args){ 3 4 //创建角色对象 5 Handler generalAuthority = new GeneralAuthority(); 6 Handler vipAuthority = new VipAuthority(); 7 Handler superVipAuthority = new SuperVipAuthority(); 8 //为当前角色设置下一个角色 9 generalAuthority.setNextHandler(vipAuthority); 10 vipAuthority.setNextHandler(superVipAuthority); 11 12 System.out.println("张三。。。。。。。。。。。。。"); 13 IUser user = new User("张三",40); 14 generalAuthority.judge(user); 15 16 System.out.println("李四。。。。。。。。。。。。。"); 17 IUser user2 = new User("李四",600); 18 generalAuthority.judge(user2); 19 } 20 21 }
控制台输出: