目录
一、设计模式六大原则
1、单一职责原则
SRP:Single Reposibility Principle
一个方法, 一个类只负责一个职责,各个职责的程序改动,不影响其它程序。
2、接口隔离原则
Interface Segregation Principle
建立单一接口,不要建立臃肿庞大的接口。接口尽量细化,同时接口中的方法尽量少。
例如:支付类的接口和订单类的接口,需要把这俩个类别的接口变成俩个隔离的接口
单一职责原则和接口隔离原则的区别:(角度不同)
-
单一职责原则是对类的约束
-
接口隔离原则是对抽象的约束
3、迪米特原则 / 最 少 知 识 原 则
LoD:Law of Demeter
具体解释:一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂,那是你的事儿,和我没关系,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关系
4、里氏替换原则
LSP:Liskov Substitution Principle
定义:多用组合,少用继承。
含义:
1> 里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。
2> 如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。
大概意思是:子类可以扩展父类的功能,但不能改变父类原有的功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,子类中可以增加自己特有的方法。
5、依赖倒置原则
DIP:Dependence Inversion Principle
定义:下层模块引入上层模块的依赖,改变原有自上而下的依赖方向。
· 依赖倒置原则的核心思想是面向接口编程.
· 依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,
· 具体内容是:对接口编程,依赖于抽象而不依赖于具体。
6、开闭原则
定义:类、方法、模块应该对扩展开放,对修改关闭。
含义:通俗讲:添加一个功能应该是在已有的代码基础上进行扩展,而不是修改已有的代码。
二、设计模式
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
1、工厂模式
简单工厂、工厂方法(套简单工厂)、抽象工厂(套工厂方法)
①不使用工厂模式:
public class BMW320 {
public BMW320(){
System.out.println("制造-->BMW320");
}
}
public class BMW523 {
public BMW523(){
System.out.println("制造-->BMW523");
}
}
public class Customer {
public static void main(String[] args) {
//直接实例
BMW320 bmw320 = new BMW320();
BMW523 bmw523 = new BMW523();
}
}
② 简单工厂:
//产品类:
//抽象产品角色
abstract class BMW {
public BMW(){
}
}
//具体产品角色
public class BMW320 extends BMW {
public BMW320() {
System.out.println("制造-->BMW320");
}
}
public class BMW523 extends BMW{
public BMW523(){
System.out.println("制造-->BMW523");
}
}
//------------------------------------------------------
//工厂类:
public class Factory {
public BMW createBMW(int type) {
switch (type) {
case 320:
return new BMW320();
case 523:
return new BMW523();
default:
break;
}
return null;
}
}
//------------------------------------------------------
//客户类:
public class Customer {
public static void main(String[] args) {
//使用工厂创建实例
Factory factory = new Factory();
BMW bmw320 = factory.createBMW(320);
BMW bmw523 = factory.createBMW(523);
}
}
③ 工厂方法
核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节
//产品类:
abstract class BMW {
public BMW(){
}
}
public class BMW320 extends BMW {
public BMW320() {
System.out.println("制造-->BMW320");
}
}
public class BMW523 extends BMW{
public BMW523(){
System.out.println("制造-->BMW523");
}
}
//------------------------------------------------------
//工厂类:
//抽象工厂角色
interface FactoryBMW {
BMW createBMW();
}
//具体工厂角色
public class FactoryBMW320 implements FactoryBMW{
@Override
public BMW320 createBMW() {
return new BMW320();
}
}public class FactoryBMW523 implements FactoryBMW {
@Override
public BMW523 createBMW() {
return new BMW523();
}
}
//------------------------------------------------------
//客户类:
public class Customer {
public static void main(String[] args) {
//实例抽象工厂
FactoryBMW320 factoryBMW320 = new FactoryBMW320();
//使用工厂方法创建实例
BMW320 bmw320 = factoryBMW320.createBMW();
FactoryBMW523 factoryBMW523 = new FactoryBMW523();
BMW523 bmw523 = factoryBMW523.createBMW();
}
}
④ 抽象工厂
抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。
(下面有抽象工厂结合策略的实例)
2、策略模式
定义:定义一组算法(相当于不同的策略),将每一个算法封装起来,从而使它们可以相互切换。因为这组算法都实现了相同的接口或者继承相同的抽象类,所以可以相互切换。
三个角色:
① 封装角色(Context):上层访问策略的入口,它持有抽象策略角色的引用。
② 抽象策略角色(Strategy):提供接口或者抽象类,定义策略组必须拥有的方法和属性。
③ 具体策略角色(ConcreteStrategyA/B/C)实现抽象策略,定义具体的算法逻辑。
//封装角色:
// Context持有Strategy的引用,并且提供了调用策略的方法
public class Context {
private Strategy strategy;
/**
* 传进的是一个具体的策略实例
* @param strategy
*/
public Context(Strategy strategy) {
this.strategy = strategy;
}
/**
* 调用策略
*/
public void contextInterface() {
strategy.algorithmLogic();
}
}
//------------------------------------------------------
// 抽象策略角色,定义了策略组的方法
public interface Strategy {
public void algorithmLogic();
}
//------------------------------------------------------
// 具体策略角色类
public class ConcreteStrategyA implements Strategy{
@Override
public void algorithmLogic() {
// 具体的算法逻辑()
}
}
//------------------------------------------------------
// 客户端
public class Client {
public static void main(String[] args) {
// 操控比赛,这场要输
Context context = new Context(new ConcreteStrategyA());
context.contextInterface();
}
}
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性非常良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露
3、工厂结合策略
//封装角色:
public class CashFactory {
/**
* 策略实例,传进一个选择策略参数
*/
public static CashSuper createCashAccept(String cashType){
/**
* 策略引用
*/
CashSuper cs = null;
switch (cashType) {
case "正常收费" :
cs = new CashNormal();
break;
case "打8折" :
cs = new CashRebate(8);
break;
case "满300减100" :
cs = new CashReturn(300, 100);
break;
default :
break;
}
return cs;
}
}
//------------------------------------------------------
// 抽象策略角色
public interface CashSuper {
/**
* 计算实收的费用
* @param money 应收金额
* @return 实收金额
*/
double acceptCash(double money);
}
//------------------------------------------------------
// 具体策略角色类
//正常收费
public class CashNormal implements CashSuper {
@Override
public double acceptCash(double money) {
return money;
}
}
//打折收费
public class CashRebate implements CashSuper {
private double moneyRebate;
/**
* @param moneyRebate 折扣率
*/
public CashRebate(double moneyRebate) {
this.moneyRebate = moneyRebate;
}
@Override
public double acceptCash(double money) {
return money * (moneyRebate / 10);
}
}
//满减收费
public class CashReturn implements CashSuper {
/**
* 应收金额
*/
private double moneyCondition;
/**
* 返利金额
*/
private double moneyReturn;
public CashReturn(double moneyCondition, double moneyReturn) {
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double acceptCash(double money) {
if (money >= moneyCondition) {
money = money - moneyReturn;
}
return money;
}
}
//------------------------------------------------------
// 客户端
public void testFactory() {
CashSuper cs = CashFactory.createCashAccept("打8折");
double result = cs.acceptCash(300);
System.out.println(result);
}
4、单例模式(面试重点)
概念:单例对象的类必须保证只有一个实例存在
适用场景: 单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
① 需要频繁实例化然后销毁的对象。
② 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
③ 有状态的工具类对象。
④ 频繁访问数据库或文件的对象。
饿汉式
public class Singleton {
/**
* 优点:没有线程安全问题,简单
* 缺点:提前初始化会延长类加载器加载类的时间;如果不使用会浪费内存空间;不能传递参数
*/
private static final Singleton instance = new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return instance;
}
}
懒汉式
public class Singleton{
/**
* 优点:解决线程安全,延迟初始化( Effective Java推荐写法)
*/
private Singleton(){}
public static Singleton getInstance () {
return Holder.SINGLE_TON;
}
private static class Holder{
private static final Singleton SINGLE_TON = new Singleton();
}
}
Java中的静态变量和静态代码块是在类加载的时候就执行的
成员变量随着对象的创建而存在,随着对象的被回收而释放;
静态变量随着类的加载而存在,随着类的消失而消失;
静态内部类和非静态内部类一样,都是在被调用时才会被加载 参考:静态内部类何时初始化 - 毛会懂 - 博客园
双重检查锁(double checked locking)
public class Singleton {
//volatile 禁止指令重排
private volatile static Singleton uniqueSingleton;
private Singleton() {
}
public Singleton getInstance() {
//第一重检查:解决线程同步带来的性能问题
if (null == uniqueSingleton) {
synchronized (Singleton.class) {
//第二重检查:检查保证单例
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();
}
}
}
return uniqueSingleton;
}
}
① volatile关键字:禁止指令重排。编译器为了性能会指令重排,例如:
uniqueSingleton = new Singleton();
分配内存空间 -》初始化对象 -》将对象指向刚分配的内存空间
重排成:
分配内存空间 -》将对象指向刚分配的内存空间 -》初始化对象
会造成多线程下访问到一个未初始化的对象问题,使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。
② 为什么要双重检查null?
(第二重检查保证单例)如果有两个线程同时到达,即同时调用getInstance() 方法,此时由于singleTon== null ,所以很明显,两个线程都可以通过第一重的 singleTon== null ,进入第一重 if语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 singleTon==null ,而另外的一个线程则会在lock 语句的外面等待。而当第一个线程执行完new SingleTon()语句后,便会退出锁定区域,此时,第二个线程便可以进入lock 语句块,此时,如果没有第二重singleTon== null 的话,那么第二个线程还是可以调用 new SingleTon()语句,这样第二个线程也会创建一个SingleTon实例,这样也还是违背了单例模式的初衷的,所以这里必须要使用双重检查锁定。
(第一重检查提高性能)对于单例模式的话,newSingleTon()只需要执行一次就 OK 了,而如果没有第一重singleTon == null 的话,每一次有线程进入getInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的,而如果我加上第一重singleTon == null 的话,那么就只有在第一次,也就是singleTton ==null 成立时的情况下执行一次锁定以实现线程同步,而以后的话,便只要直接返回Singleton 实例就 OK 了而根本无需再进入 lock语句块了,这样就可以解决由线程同步带来的性能问题了。
单例模式的破坏
反射和序列化会破坏单例模式,解决方案:重写两个反射和序列化用到的方法
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
private Object readResolve() {
return SingletonInstance.INSTANCE;
}
单例对象生命周期
出生:容器创建时对象出生。(立即创建)
活着:只要容器在,对象一直活着。
死亡:容器销毁,对象消亡。
总结:单例对象与容器共存亡 。
多例对象生命周期
出生:当我们使用对象时,Spring框架为我们创建对象。(延迟创建)
活着:对象只要在使用过程中就一直活着。
死亡:当对象长时间不用且没有别的对象引用时,由Java的垃圾回收器回收。
工具类用单例模式还是静态方法(看情况)
如果没有配置信息的工具类,当然是静态类好,随处调用,不需引用
如果有配置信息的工具类,最好还是使用单例模式,例如多数据源连接工具
5、命令模式
背景:当需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,实现请求发送者与请求接收者间解耦
定义:将”请求“封装成对象,以便使用不同的请求、队列或日志来参数化其他对象。命令模式也支持可撤销的操作。
应用:操作数据库的 redo/undo
6、代理模式(用的多)
什么是代理模式?
· 通过代理控制对象的访问,可以在这个对象调用方法之前、调用方法之后去处理/添加新的功能。(AOP的实现)
· 代理在原有代码乃至原业务流程都不修改的情况下,直接在业务流程中切入新代码,增加新功能,这也和Spring的(面向切面编程)很相似
代理模式应用场景
Spring AOP、日志打印、异常处理、事务控制、权限控制等
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。
分类:
静态代理
三种角色:
① 抽象角色:声明真实对象和代理对象的共同接口
② 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
③ 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
/**
* 抽象角色
*/
public abstract class Subject {
public abstract void request();
}
//------------------------------------------------------
/**
* 真实的角色
*/
public class RealSubject extends Subject {
@Override
public void request() {
// TODO Auto-generated method stub
}
}
//------------------------------------------------------
/**
* 静态代理,对具体真实对象直接引用
* 代理角色,代理角色需要有对真实角色的引用,
* 代理做真实角色想做的事情
*/
public class ProxySubject extends Subject {
private RealSubject realSubject = null;
/**
* 除了代理真实角色做该做的事情,代理角色也可以提供附加操作,
* 如:preRequest()和postRequest()
*/
@Override
public void request() {
preRequest(); //真实角色操作前的附加操作
if(realSubject == null){
realSubject = new RealSubject();
}
realSubject.request();
postRequest(); //真实角色操作后的附加操作
}
/**
* 真实角色操作前的附加操作
*/
private void postRequest() {
// TODO Auto-generated method stub
}
/**
* 真实角色操作后的附加操作
*/
private void preRequest() {
// TODO Auto-generated method stub
}
}
//------------------------------------------------------
/**
* 客户端调用
*/
public class Main {
public static void main(String[] args) {
Subject subject = new ProxySubject();
subject.request(); //代理者代替真实者做事情
}
}
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.
缺点:每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。
jdk动态代理
动态代理解决静态代理中代理类接口过多的问题,通过反射来实现的,借助Java自带的java.lang.reflflect.Proxy,通过固定的规则生成。
步骤如下:
-
编写一个委托类的接口,即静态代理的(Subject接口)
-
实现一个真正的委托类,即静态代理的(RealSubject类)
-
创建一个动态代理类,实现InvocationHandler接口,并重写该invoke方法
-
在测试类中,生成动态代理的对象。
第一二步骤,和静态代理一样,第三步:创建动态代理类
public class DynamicProxy implements InvocationHandler {
private Object object;
public DynamicProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//实现前置操作
Object result = method.invoke(object, args);
//实现后置操作
return result;
}
}
第四步:创建动态代理的对象
Subject realSubject = new RealSubject();
DynamicProxy proxy = new DynamicProxy(realSubject);
ClassLoader classLoader = realSubject.getClass().getClassLoader();
Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new Class[] {Subject.class}, proxy);
subject.visit();
上述代码的关键是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler handler)方法,该方法会根据指定的参数动态创建代理对象。三个参数的意义如下:
-
loader,指定代理对象的类加载器;
-
interfaces,代理对象需要实现的接口,可以同时指定多个接口;(jdk代理必须要有接口,和cglib动态代理的区别)
-
handler,方法调用的实际处理者,代理对象的方法调用都会转发到这里
cglib动态代理
没有实现接口的类,只能使用cglib动态代理
//被代理对象类
public class UserDaoImpl {
public void save() {
System.out.println("保存数据方法");
}
}
//------------------------------------------------------
// CGLIB动态代理
// 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
public class CglibProxy implements MethodInterceptor {
private Object targetObject;
// 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
public Object getInstance(Object target) {
// 设置需要创建子类的类
this.targetObject = target;
// 2. 通过CGLIB动态代理获取代理对象。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
//代理实际方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//在可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等
System.out.println("开启事物");
Object result = methodProxy.invoke(targetObject, objects);
System.out.println("关闭事物");
// 返回代理对象
return result;
}
}
//------------------------------------------------------
//测试CGLIB动态代理
public class Test {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
UserDaoImpl userDao = (UserDaoImpl) cglibProxy.getInstance(new UserDaoImpl());
userDao.save();
}
}
//------------------------------------------------------
//输出结果:
开启事物
保存数据方法
关闭事物
① 通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象
② 通过调用create()方法得到代理对象,对这个对象所有非fifinal方法的调用都会转发给MethodInterceptor.intercept()方法
③ 在intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;
④ 通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是HelloConcrete的具体方法。
对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不会,因为它是fifinal方法,CGLIB无法代理。
原理:
CGLIB是一个强大的高性能的代码生成包,底层是通过使用一个小而快的字节码处理框架ASM,它可以在运行期扩展Java类与实现Java接口,Enhancer是CGLIB的字节码增强器,可以很方便的对类进行拓展创建代理对象的几个步骤:
1、生成代理类的二进制字节码文件
2、加载二进制字节码,生成Class对象( 例如使用Class.forName()方法 )
3、通过反射机制获得实例构造,并创建代理类对象
jdk动态代理和cglib动态代理的区别:
-
jdk动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。只能对实现了接口的类生成代理。
-
cglib动态代理:利用ASM开源包,把代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,对于fifinal类或方法,是无法继承的。
-
选择:
① 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
② 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
③ 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
7、模板方法模式
什么是模板方法
模板方法模式:定义一个操作中的算法骨架(父类),而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构来重定义该算法的.
什么时候使用模板方法
实现一些操作时,整体步骤很固定,但是呢。就是其中一小部分需要改变,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
实际开发中应用场景哪里用到了模板方法
例如:数据库访问的封装、Junit单元测试、servlet中关于doGet/doPost方法的调用等等
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
//例如:去餐厅吃饭,餐厅给我们提供了一个模板就是:看菜单,点菜,吃饭,付款,走人 (这里 “点菜和付款” 是不确定的由子类来完成的,其他的则是一个模板。)
//先定义一个模板。把模板中的点菜和付款,让子类来实现。
//模板方法
public abstract class RestaurantTemplate {
// 1.看菜单
public void menu() { System.out.println("看菜单"); }
// 2.点菜业务
abstract void spotMenu();
// 3.吃饭业务
public void havingDinner(){ System.out.println("吃饭"); }
// 4.付款业务
abstract void payment();
// 5.走人
public void GoR() { System.out.println("走人"); }
//模板通用结构
public void process(){
menu();
spotMenu();
havingDinner();
payment();
GoR();
}
}
//------------------------------------------------------
//具体的模板方法子类 1
public class RestaurantGinsengImpl extends RestaurantTemplate {
void spotMenu() { System.out.println("人参"); }
void payment() { System.out.println("5快"); }
}
//具体的模板方法子类 2
public class RestaurantLobsterImpl extends RestaurantTemplate {
void spotMenu() { System.out.println("龙虾"); }
void payment() { System.out.println("50块"); }
}
//------------------------------------------------------
//客户端测试
public class Client {
public static void main(String[] args) {
//调用第一个模板实例
RestaurantTemplate restaurantTemplate = new RestaurantGinsengImpl();
restaurantTemplate.process();
}
}
8、适配器模式
意图:将一个类的接口转换成客户希望的另外一个接口
如何解决:继承或依赖(推荐)
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
9、装饰器模式
允许向一个现有的对象添加新的功能,同时又不改变其结构
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
10、观察者模式
意图:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。每次改变都遍历观察者列表,并调用观察者通知方法
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
//发布者 subject 类
public class Subject {
//观察者列表
private List<Observer> observers = new ArrayList<Observer>();
//状态信息
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer){
observers.add(observer);
}
//调用观察者的获取订阅者状态方法
public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
//------------------------------------------------------
//创建抽象 Observer 类
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
//创建实体观察者类。
public class BinaryObserver extends Observer{
//绑定发布者
public BinaryObserver(Subject subject){
this.subject = subject;
//发布者提供订阅者信息
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) );
}
}
//------------------------------------------------------
//测试类
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
//new HexaObserver(subject);
//new OctalObserver(subject);
new BinaryObserver(subject);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}