文章目录
1、创建者模式
1.1 单例模式
- 看这个:单例模式
1)什么是单利模式、为什么用单例模式:
- 一个类中只能创建一个实例,所以称之为单例!
- 在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
2)单例模式的实现步骤:
- 将构造函数私有化
- 在类的内部创建实例
- 提供获取唯一实例的方法
3)五种单例模式的写法:
-
饿汉式
-
简单懒汉式(在实例的方法上加锁)
-
DCL双重检测加锁(进阶懒汉式,在实例的方法内部加锁,并在锁内与锁外各做一次检测判null)
-
静态内部类实现懒汉式(线程安全,最推荐写法)
public class Singleton { //静态内部类 private static class SingletonHolder { //静态常量 private static final Singleton INSTANCE = new Singleton(); } //构造方法 private Singleton() { } //实例方法 public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
-
枚举方式(最安全、简洁写法)
public enum Singleton { // 定义一个枚举的元素,它就是 Singleton 的一个实例 INSTANCE; public void doSomeThing() { System.out.println("枚举方法实现单例"); } public static void main(String[] args) { Singleton singleton = Singleton.INSTANCE; singleton.doSomeThing();// output:枚举方法实现单例 } }
1.2 工厂模式
- 看这个:工厂模式
1)什么是工厂模式:
- 在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行。
2)为什么要用工厂模式:
-
解耦 :把对象的创建和使用的过程分开,创建交由工厂类完成。
-
降低代码重复: 如果创建某个对象的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。
-
降低维护成本 :由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建对象B的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。
3)工厂模式的分类:
-
简单工厂模式,又称静态工厂方法模式;提供一个统一的工厂类来创建所有的对象,但是不符合开放-封闭原则 。
-
工厂方法模式,又称多态性工厂模式或虚拟构造子模式;针对不同的对象提供不同的工厂,也就是说:每个对象都有一个与之对应的工厂类。
-
抽象工厂模式,又称工具箱模式;抽象工厂是生产一整套有产品的(至少要生产两个产品),这些产品必须相互是有关系或有依赖的,而工厂方法中的工厂是生产单一产品的工厂。
2、结构性模式
2.1代理模式
- 看这个代理模式
1)什么是代理模式:
- 给某一个对象提供一个代理,并由代理对象控制对原对象的引用。其特征是代理类与委托类有同样的接口。
2)为什么要用代理模式:
- 在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
3)代理模式的分类:
-
静态代理(“聚合”和“继承 ”两种方式实现,特点是一个主题类与一个代理类一一对应)
a.聚合方式实现:
/**方式一: *聚合式静态代理 *委托类和代理类实现同一个接口 *但是代理类的构造函数式对委托类的一个实例函数 *或者通过传递参数进行实例 */ //1.抽象主题接口 public interface Manager { void doSomething(); } //2.真实主题类,也就是将要委托的类 public class Admin implements Manager { public void doSomething() { System.out.println("Admin do something."); } } //3.以聚合方式实现的代理主题,也就是代理类 public class AdminPoly implements Manager{ private Admin admin; public AdminPoly(Admin admin) { super(); this.admin = admin; } public void doSomething() { System.out.println("Log:admin操作开始"); admin.doSomething(); System.out.println("Log:admin操作结束"); } } //如果代理类的构造函数不传递参数,则这个代理类就只能代理一个委托类 public class AdminPoly implements Manager{ private Admin admin; public AdminPoly() { super(); this.admin = new Admin(); } public void doSomething() { System.out.println("Log:admin操作开始"); admin.doSomething(); System.out.println("Log:admin操作结束"); } } //4.测试代码 Admin admin = new Admin(); Manager m = new AdminPoly(admin); m.doSomething();
b.继承方式实现:
/*方式二:继承式静态代理 *让代理类去继承委托类,当然这个就没有前面一种方式灵活 */ //与上面的方式仅代理类和测试代码不同 //1.代理类 public class AdminProxy extends Admin { @Override public void doSomething() { System.out.println("Log:admin操作开始"); super.doSomething(); System.out.println("Log:admin操作开始"); } } //2.测试代码 AdminProxy proxy = new AdminProxy(); proxy.doSomething();
-
动态代理(JDK实现 和 cglib实现)
a、JDK实现步骤:
-
创建一个实现InvocationHandler接口的类,它必须实现invoke()方法
-
创建被代理的类及接口
-
调用Proxy的静态方法,创建一个代理类
-
通过代理调用方法
//1. 抽象主题,委托类接口 public interface Moveable { void move() throws Exception; } //2. 真实主题,委托类 public class Car implements Moveable { public void move() throws Exception { Thread.sleep(new Random().nextInt(1000)); System.out.println("汽车行驶中…"); } } //3.事务处理器 public class TimeHandler implements InvocationHandler { private Object target; public TimeHandler(Object target) { super(); this.target = target; } /** * 实现invoke方法 * @param proxy 被代理的对象 * @param method 被代理对象的方法 * @param args 方法的参数 * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶…"); method.invoke(target, args); long stopTime = System.currentTimeMillis(); System.out.println("汽车结束行驶…汽车行驶时间:" + (stopTime - startTime) + "毫秒!"); return null; } } //测试类 public class Test { public static void main(String[] args) throws Exception{ Car car = new Car(); InvocationHandler h = new TimeHandler(car); Class<?> cls = car.getClass(); /** *loader 类加载器 *interfaces 实现接口 *h InvocationHandler */ Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h); m.move(); } }
b、cglib 实现:
//1.具体主题 public class Train{ public void move(){ System.out.println("火车行驶中…"); } } //2.生成代理 public class CGLibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class<?> clazz){ enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } /** * 拦截所有目标类方法的调用 * 参数: * obj目标实例对象 *method 目标方法的反射对象 * args方法的参数 * proxy代理类的实例 */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { //代理类调用父类的方法 System.out.println("日志开始"); proxy.invokeSuper(obj, args); System.out.println("日志结束"); return null; } } //3.测试 public class Test { public static void main(String[] args) { CGLibProxy proxy = new CGLibProxy(); Train t = (Train) proxy.getProxy(Train.class); t.move(); } }
-
4)静态代理与动态代理的区别:
- 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样对每一个方法或方法组合进行处理。Proxy 很美很强大,但是仅支持 interface 代理。Java 的单继承机制注定了这些动态代理类们无法实现对 class 的动态代理。好在有cglib为Proxy提供了弥补。
2.2 适配器模式
- 看这里:适配器模式
1)什么是适配器模式:
- 适配器模式,把一个类的接口变化成客户端所期待的另一个类的接口,使原来因接口不匹配而无法一起工作的类能够一起工作。
- 用电器做例子,一个电器的插头只有两相,而有些地方的电源插座却只有三相。电源插座与电器的电源插头不匹配使得电器无法使用。这时候一个三相到两相的转换器(适配器)就能解决此问题,而这正像是本模式所做的事情。
2)适配器模式结构
- Target(目标角色): 客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
- Adaptee(源角色):现在需要适配的类。
- Adapter(适配器): 适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
3)适配器模式的分类:
-
类适配器
通过 使适配器(类)继承源角色(类)并实现目标角色(接口),在目标角色的指定方法中调用源角色的指定方法,从而达到适配的作用。(因为 Java 不支持多继承,所以这样来实现) -
对象适配器
不再使用多继承或继承再实现的方式,而是使用直接关联,或者称为委托的方式,通过组合的方式跟适配对象组合。也就是说:只让适配器(类)实现目标角色(接口),然后在适配器中创建一个源角色类型的成员变量,等实例化适配器的时候,只要给构造函数传递一个源角色的对象实例就ok。
2.3 装饰模式(包装模式)
- 看这个:装饰模式
1)什么是装饰模式:
- 装饰者模式又称为包装(wrapper)模式。装饰者模式对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。也就是说,在原来的基础上,对原来的类进行扩展,增添一些属性和方法,或者对某个方法增强修改功能。这种对类的扩展方式,也可以使用继承的方式实现,但是如果使用继承的话,当扩展的功能发生变化的时候,就不好更改。
2)为什么要用装饰模式:
- 首先,装饰器的价值在于装饰,他并不影响被装饰类本身的核心功能。在一个继承的体系中,子类通常是互斥的。比如一辆车,品牌只能要么是奥迪、要么是宝马,不可能同时属于奥迪和宝马,而品牌也是一辆车本身的重要属性特征。但当你想要给汽车喷漆,换坐垫,或者更换音响时,这些功能是互相可能兼容的,并且他们的存在不会影响车的核心属性:那就是他是一辆什么车。这时你就可以定义一个装饰器:喷了漆的车。不管他装饰的车是宝马还是奥迪,他的喷漆效果都可以实现。
3)装饰模式的实现步骤:(如果只有前三个步骤的话就是一个静态代理模式)
-
抽象构件(Component)角色:给出一个抽象接口,以规范准备接受附加责任的对象。
-
具体构件(ConCreteComponent)角色:定义一个将要接受附加责任的类。也就是实现了前面接口的类。
-
装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
-
具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。继承装饰角色,给要装饰的方法附上要装饰的内容。
//基础接口 public interface Component { public void biu(); } //具体实现类 public class ConcretComponent implements Component { public void biu() { System.out.println("biubiubiu"); } } //装饰类 public class Decorator implements Component { public Component component; public Decorator(Component component) { this.component = component; } public void biu() { this.component.biu(); } } //具体装饰类 public class ConcreteDecorator extends Decorator { public ConcreteDecorator(Component component) { super(component); } public void biu() { System.out.println("ready?go!"); this.component.biu(); } }
4)装饰模式与静态代理模式的区别:
- 代理类和被代理类都要实现同一个接口,装饰类和被装饰类也都要实现同一个类,但是两者也是有区别的。代理模式主要是控制对某个特定对象访问,而装饰模式主要是为了给对象添加行为。
2.4 外观模式(门面模式)
- 看这里:外观模式
1)什么是门面模式:
- 定义了一个高层接口,为子系统中的一组接口提供了一个一致的界面,从而使得这一组子系统更加容易使用。 也就是说,用一个类对子系统进行了一次封装,让其他的类调用得时候通过这个封装类来调用,这就是门面模式,就这么简单。
2)门面模式实现的步骤:
-
子系统角色:
public class ModuleA { //示意方法 public void TestA(){ System.out.println("调用ModuleA中的testA方法"); } } public class ModuleB { //示意方法 public void TestB(){ System.out.println("调用ModuleB中的testB方法"); } } public class ModuleC { //示意方法 public void testC(){ System.out.println("调用ModuleC中的testC方法"); } }
-
门面角色:
public class Facade { //示意方法,满足客户端需要的功能 public void test(){ ModuleA a = new ModuleA(); a.testA(); ModuleB b = new ModuleB(); b.testB(); ModuleC c = new ModuleC(); c.testC(); } }
-
客户端
public class Client { public static void main(String[] args) { Facade facade = new Facade(); facade.test(); } }
2.5 组合模式(部分整体模式)
- 看这里:组合模式
1)什么是组合模式:
- 用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
3、行为型模式
3.1 责任链模式
看这个:责任链模式
1)什么是责任链模式:
- 使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
2)为什么要用责任链模式(优缺点):
- 优点
- 将请求者和处理者分开,两者解耦,提供系统灵活性。
- 缺点
- 请求都是从链头遍历到链尾,当链很长的时候,性能是个很大的问题。
- 一般我们可以在 Handler 中设置一个最大节点数量,在 setNext 方法中判断是否已经超过最大节点数,超过则不允许继续添加处理者,避免无意识的破坏系统性能。
3)责任链模式结构:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
3.2 观察者模式
- 看这里:观察者模式
1)什么是观察者模式:
- 观察者模式定义了一个一对多的依赖关系,让多个观察者对象同时监听同一个主题对象。当这个主题状态发生改变时,会通知所有观察者对象,让它们自动更新自己。
2)为什么要用观察者模式:
- 此设计模式最重要的作用就是 解耦!将观察者与被观察者解耦,使得他们之间的依赖性更小。
3)观察者模式的结构:
-
抽象观察者角色(Observer): 为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
//抽象观察类 interface Observer { public String getName(); public void setName(String name); public void help(); //声明支援盟友方法 public void beAttacked(AllyControlCenter acc); //声明遭受攻击方法 }
-
具体观察者角色(ConcreteObserver): 该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
//战队成员类:具体观察者类 class Player implements Observer { private String name; public Player(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } //支援盟友方法的实现 public void help() { System.out.println("坚持住," + this.name + "来救你!"); } //遭受攻击方法的实现,当遭受攻击时将调用战队控制中心类的通知方法notifyObserver()来通知盟友 public void beAttacked(AllyControlCenter acc) { System.out.println(this.name + "被攻击!"); acc.notifyObserver(name); } }
-
抽象主题角色(Subject): 把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。就是“被观察”的角色,它将所有观察者对象的引用保存在一个集合中。
//战队控制中心类:目标类 abstract class AllyControlCenter { protected String allyName; //战队名称 protected ArrayList<Observer> players = new ArrayList<Observer>(); //定义一个集合用于存储战队成员 public void setAllyName(String allyName) { this.allyName = allyName; } public String getAllyName() { return this.allyName; } //注册方法 public void join(Observer obs) { System.out.println(obs.getName() + "加入" + this.allyName + "战队!"); players.add(obs); } //注销方法 public void quit(Observer obs) { System.out.println(obs.getName() + "退出" + this.allyName + "战队!"); players.remove(obs); } //声明抽象通知方法 public abstract void notifyObserver(String name); }
-
具体主题角色(ConcreteSubject): 在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
//具体战队控制中心类:具体目标类 class ConcreteAllyControlCenter extends AllyControlCenter { public ConcreteAllyControlCenter(String allyName) { System.out.println(allyName + "战队组建成功!"); System.out.println("----------------------------"); this.allyName = allyName; } //实现通知方法 public void notifyObserver(String name) { System.out.println(this.allyName + "战队紧急通知,盟友" + name + "遭受敌人攻击!"); //遍历观察者集合,调用每一个盟友(自己除外)的支援方法 for(Object obs : players) { if (!((Observer)obs).getName().equalsIgnoreCase(name)) { ((Observer)obs).help(); } } } }
4)客户端测试代码:
class Client {
public static void main(String args[]) {
//定义观察目标对象
AllyControlCenter acc;
acc = new ConcreteAllyControlCenter("金庸群侠");
//定义四个观察者对象
Observer player1,player2,player3,player4;
player1 = new Player("杨过");
acc.join(player1);
player2 = new Player("令狐冲");
acc.join(player2);
player3 = new Player("张无忌");
acc.join(player3);
player4 = new Player("段誉");
acc.join(player4);
//某成员遭受攻击
Player1.beAttacked(acc);
}
}
3.3 策略模式
- 看这里:策略模式
1)什么是策略模式:
- 策略(strategy)模式属于对象的行为模式。其用途是针对一组算法,将每个不同算法封装到具有共同接口的独立类中,从而使他们可以相互替换。即是: 算法和对象分开来,使得算法可以独立于使用它的客户而变化。
2)为什么要用策略模式(优缺点):
-
优点:
-
策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
-
使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
-
-
缺点:
-
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
-
由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
-
3)策略模式结构
-
Context环境角色 : 持有一个Strategy的引用。
public class Context { private Strategy strategy;//持有策略引用 public Context(Strategy strategy) { super(); this.strategy = strategy; } public void printPrice(double price ){ System.out.println("价格为:"+strategy.getPrice(price)); } }
-
Strategy抽象策略角色: 这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
public interface Strategy { /** * * @param price 商品原价 * @return 打折后价格 */ public double getPrice(double price); }
-
ConcreteStrategy具体策略角色: 包装了相关的算法或行为。
public class NewCustomerFewStrategy implements Strategy{ public double getPrice(double price) { System.out.println("普通客户小批量,不打折"); return price; } } public class NewCustomerManyStrategy implements Strategy{ public double getPrice(double price) { System.out.println("普通客户大批量,打9折"); return price*0.9; } } public class OldCustomerFewStrategy implements Strategy{ public double getPrice(double price) { System.out.println("老客户小批量,打8.5折"); return price*0.85; } } public class OldCustomerManyStrategy implements Strategy{ public double getPrice(double price) { System.out.println("老客户大批量,打8折"); return price; } }
4)客户端测试代码:
public class Client {
public static void main(String[] args) {
Strategy strategy= new NewCustomerFewStrategy();
Context context= new Context(strategy);
context.printPrice(100);
}
}
//输出结果
普通客户小批量,不打折
价格为:100.0
3.4 命令模式
- 看这里:命令模式
1)什么是命令模式:
- 将一个请求封装为对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。也称为:动作(Action)模式、事务(transaction)模式。
2)为什么要用命令模式(优缺点):
-
优点:
- 降低对象之间的耦合度。
- 新的命令可以很容易地加入到系统中。
- 可以比较容易地设计一个组合命令。
- 调用同一方法实现不同的功能。
-
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
3)命令模式结构:
-
Command抽象命令类: 声明执行操作的接口。
/** * 抽象命令接口 */ public interface Command { void execute();//执行命令 void cancel();//取消命令 }
-
ConcreteComand具体命令类:通常持有 一个接收者对象并绑定于一个动作, 调用接收者相应的操作,以实现execute方法。
/** * 具体命令类 */ public class ConcreteCommand implements Command{ private Receiver receiver; public ConcreteCommand(Receiver receiver) { super(); this.receiver = receiver; } public void execute() { //可进行执行命令前相关操作 receiver.action();//执行命令 //可进行执行命令后相关操作 } public void cancel() { receiver.unAction(); } }
-
Invoker调用者/请求者: 请求的发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联。在程序运行时,将调用命令对象的execute(),间接调用接收者的相关操作。
public class Invoker { private Command command; public Invoker(Command command) { super(); this.command = command; } //执行命令 public void runCommand(){ command.execute(); } //取消命令 public void cancelCommand(){ command.cancel(); } }
-
Receiver接收者(执行者):接收者执行与请求相关的操作,具体实现对请求的业务处理。
/** * 真正的命令执行者 */ public class Receiver { public void action(){ System.out.println("执行命令----"); } public void unAction(){ System.out.println("撤销命令----"); } }
4)客户端测试代码:
public class Client {
public static void main(String[] args) {
//创建接收者
Receiver receiver=new Receiver();
//创建命令对象,并设置接收者
Command command=new ConcreteCommand(receiver);
//创建调用者,设置命令
Invoker invoker=new Invoker(command);
invoker.runCommand();
invoker.cancelCommand();
}
}
//输出结果
执行命令----
撤销命令----