Java 中一般认为有23种设计模式,当然暂时不需要所有的都会,但是其中常见的几种设计模式应该去掌握。
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式原则
很多优秀的文章和书籍都讲的很明白了,我说下自己的体会。
1.单一职责原则,就是一个类只负责做一件事情。这样就可以做到解耦合的效果,让代码看起来比较清爽,也体现了java的封装性。还有个原则叫迪米特法则,就是一个对象对另一个对象有尽量少的了解,说的也是解耦合的事情。
2.里氏替换原则和依赖导致原则,说的是继承的事情。父类可以做的事情,子类都可以去做,子类可以尽量去依赖父类去做事情;但是反过来,父类不能依赖子类去做一些事情。体现了java的继承特性。
3.接口隔离原则,接口也应该尽可能的隔离开来。其实类写多了,的确耦合性低,为了让他们交流起来,用的最多的就是接口,毕竟只需要知道做什么,怎么做,去访问那个具体的类吧。
4.开闭原则,对修改关闭,对拓展开放。就是代码需要有很好的延展性,对原有代码结构不能破坏。
参考博文链接
https://blog.csdn.net/weixin_40834464/article/details/82958187
https://www.cnblogs.com/V1haoge/p/6479118.html
https://github.com/kkzhilu/DesignPattern
https://blog.csdn.net/yubujian_l/article/details/81455524
https://developer.51cto.com/art/201907/599580.htm
1.创建型模式
创建者模式就是为了用优雅的方式创建我们使用的类
(1)单例模式
所谓的单例设计指的是一个类只允许产生一个实例化对象。
最好理解的一种设计模式,分为懒汉式和饿汉式。
饿汉式:构造方法私有化,外部无法产生新的实例化对象,只能通过static方法取得实例化对象
class Singleton {
/**
* 在类的内部可以访问私有结构,所以可以在类的内部产生实例化对象
*/
private static Singleton instance = new Singleton();
/**
* private 声明构造
*/
private Singleton() {
}
/**
* 返回对象实例
*/
public static Singleton getInstance() {
return instance;
}
public void print() {
System.out.println("Hello Singleton...");
}
}
懒汉式:当第一次去使用Singleton对象的时候才会为其产生实例化对象的操作
class Singleton {
/**
* 声明变量
*/
private static volatile Singleton singleton = null;
/**
* 私有构造方法
*/
private Singleton() {
}
/**
* 提供对外方法
* @return
*/
public static Singleton getInstance() {
// 还未实例化
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
public void print() {
System.out.println("Hello World");
}
}
当多个线程并发执行 getInstance 方法时,懒汉式会存在线程安全问题,所以用到了 synchronized 来实现线程的同步,当一个线程获得锁的时候其他线程就只能在外等待其执行完毕。而饿汉式则不存在线程安全的问题。
(2) 工厂方法模式
工厂方法模式:
- 工厂方法模式分为三种:普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
- 多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
- 静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
1. 普通工厂模式
建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
这个用的比较少,就是有个工厂,告诉你我要什么东西,你造好了给我就行。比如说:
interface Sender {
void Send();
}
class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}
public class FactoryPattern {
public static void main(String[] args) {
Sender sender = produce("mail");
sender.Send();
}
public static Sender produce(String str) {
if ("mail".equals(str)) {
return new MailSender();
} else if ("sms".equals(str)) {
return new SmsSender();
} else {
System.out.println("输入错误...");
return null;
}
}
}
2. 多个工厂方法模式
该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
interface Sender {
void Send();
}
class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}
class SendFactory {
public Sender produceMail() {
return new MailSender();
}
public Sender produceSms() {
return new SmsSender();
}
}
public class FactoryPattern {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
3. 静态工厂方法模式
将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
interface Sender {
void Send();
}
class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}
class SendFactory {
public static Sender produceMail() {
return new MailSender();
}
public static Sender produceSms() {
return new SmsSender();
}
}
public class FactoryPattern {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
(3)抽象工厂模式
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要扩展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?
那么这就用到了抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
interface Provider {
Sender produce();
}
interface Sender {
void Send();
}
class MailSender implements Sender {
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
public void Send() {
System.out.println("This is sms sender...");
}
}
class SendMailFactory implements Provider {
public Sender produce() {
return new MailSender();
}
}
class SendSmsFactory implements Provider {
public Sender produce() {
return new SmsSender();
}
}
public class FactoryPattern {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
(4)建造者模式
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性。
将一个复杂对象分布创建。如果一个超大的类的属性特别多,我们可以把属性分门别类,不同属性组成一个稍微小一点的类,再把好几个稍微小点的类窜起来。比方说一个电脑,可以分成不同的稍微小点的部分CPU、主板、显示器。CPU、主板、显示器分别有更多的组件,不再细分。
import java.util.ArrayList;
import java.util.List;
/**
* @Author: LiuWang
* @Created: 2018/8/6 17:47
*/
abstract class Builder {
/**
* 第一步:装CPU
*/
public abstract void buildCPU();
/**
* 第二步:装主板
*/
public abstract void buildMainBoard();
/**
* 第三步:装硬盘
*/
public abstract void buildHD();
/**
* 获得组装好的电脑
* @return
*/
public abstract Computer getComputer();
}
/**
* 装机人员装机
*/
class Director {
public void Construct(Builder builder) {
builder.buildCPU();
builder.buildMainBoard();
builder.buildHD();
}
}
/**
* 具体的装机人员
*/
class ConcreteBuilder extends Builder {
Computer computer = new Computer();
@Override
public void buildCPU() {
computer.Add("装CPU");
}
@Override
public void buildMainBoard() {
computer.Add("装主板");
}
@Override
public void buildHD() {
computer.Add("装硬盘");
}
@Override
public Computer getComputer() {
return computer;
}
}
class Computer {
/**
* 电脑组件集合
*/
private List<String> parts = new ArrayList<String>();
public void Add(String part) {
parts.add(part);
}
public void print() {
for (int i = 0; i < parts.size(); i++) {
System.out.println("组件:" + parts.get(i) + "装好了...");
}
System.out.println("电脑组装完毕...");
}
}
public class BuilderPattern {
public static void main(String[] args) {
Director director = new Director();
Builder builder = new ConcreteBuilder();
director.Construct(builder);
Computer computer = builder.getComputer();
computer.print();
}
}
例2
// 1.需要的对象定义:产品(Product)
public class Human {
private String head;
private String body;
private String hand;
private String foot;
public String getHead() {
return head;
}
public void setHead(String head) {
this.head = head;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getHand() {
return hand;
}
public void setHand(String hand) {
this.hand = hand;
}
public String getFoot() {
return foot;
}
public void setFoot(String foot) {
this.foot = foot;
}
@Override
public String toString() {
return "Human [head=" + head + ", body=" + body + ", hand=" + hand + ", foot=" + foot + "]";
}
}
// 2.定义需要对象应有的方法及返回对象的抽象方法 --- 建造者角色(Builder)
public interface IBuildHuman {
public void buildHead();
public void buildBody();
public void buildHand();
public void buildFoot();
public Human createHuman();
}
// 3.实现类实现抽象方法,进行建造 --- 具体创建者角色(ConcreteBuilder)
public class SmartManBuilder implements IBuildHuman {
Human human;
public SmartManBuilder() {
human = new Human();
}
@Override
public void buildHead() {
human.setHead("头脑智商180");
}
@Override
public void buildBody() {
human.setBody("身体");
}
@Override
public void buildHand() {
human.setHand("手");
}
@Override
public void buildFoot() {
human.setFoot("脚");
}
@Override
public Human createHuman() {
return human;
}
}
// 3.实现类实现抽象方法,进行建造 --- 具体创建者角色 当前为运动员
public class ActiveManBuilder implements IBuildHuman {
Human human;
public ActiveManBuilder() {
human = new Human();
}
@Override
public void buildHead() {
human.setHead("头脑智商180");
}
@Override
public void buildBody() {
human.setBody("身体无敌的运动员");
}
@Override
public void buildHand() {
human.setHand("手");
}
@Override
public void buildFoot() {
human.setFoot("脚");
}
@Override
public Human createHuman() {
return human;
}
}
// 4.建造模式的核心 --- 指导者(Director,进行建造组装)
public class Director {
public Human createHumanByDirecotr(IBuildHuman bh) {
bh.buildBody();
bh.buildFoot();
bh.buildHand();
bh.buildHead();
return bh.createHuman();
}
}
/***
* 创建型模式 ---- 建造者模式:
* 1.需要的对象定义:产品(Product)
* 2.定义需要对象应有的方法及返回对象的抽象方法 --- 建造者角色(Builder)
* 3.实现类实现抽象方法,进行建造 --- 具体创建者角色(ConcreteBuilder)
* 4.建造模式的核心 --- 指导者(Director)
* @author kxm
*/
public class BuildTest {
public static void main(String[] args) {
Director director = new Director();
Human human = director.createHumanByDirecotr(new SmartManBuilder());
Human humanAgain = director.createHumanByDirecotr(new ActiveManBuilder());
System.out.println(human);
System.out.println(humanAgain);
}
}
测试结果:
Human [head=头脑智商180, body=身体, hand=手, foot=脚]
Human [head=头脑智商180, body=身体无敌的运动员, hand=手, foot=脚]
(5)原型模式
原型模式用的比较少,用于创建重复对象。需要实现Cloneable 可以选择重写clone()方法。clone分为浅克隆和深克隆。浅克隆只是克隆引用,对象还是一个。深克隆是对象也新创建了一个,如下:
2.结构型模式
(1)适配器设计模式
适配器就是一种适配中间件,它存在于不匹配的二者之间,用于连接二者,将不匹配变得匹配,简单点理解就是平常所见的转接头,转换器之类的存在。
适配器模式是将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的的类的兼容性问题。主要分三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
前两种的主要作用相当于生活中的真实适配器,如5v的充电器需要插入220v的电压,这里就需要适配器
另外一种是接口适配器,如MouseAdapter,我们只需要重写我们需要的方法,不需要的方法就不管它,可以大大减少我们的代码量,提高效率
参考博客:https://www.cnblogs.com/V1haoge/p/6479118.html
1. 类的适配器模式:
比如,我们现在有USB标准接口,但是PS2也想插入用一下,直接使用是不行的,因此需要适配器
原理:通过继承来实现适配器功能。
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后再继承接口B的实现类BB,这样我们可以在适配器P中访问接口B的方法了,这时我们在适配器P中的接口A方法中直接引用BB中的合适方法,这样就完成了一个简单的类适配器。
详见下方实例:我们以ps2与usb的转接为例
public interface Usb {
void isUsb();
}
public interface Ps2 {
void isPs2();
}
public class Usber implements Usb {
@Override
public void isUsb() {
System.out.println("USB口");
}
}
创建适配器:
类适配器实现思路: 适配器继承标准类,实现需要适配的接口,实现接口内方法即可
/***
* 类适配器实现思路: 适配器继承标准类,实现需要适配的接口,实现接口内方法即可
* @author kxm
*/
public class UsbAdapter extends Usber implements Ps2 {
@Override
public void isPs2() {
super.isUsb();
}
}
测试类:
/***
* 结构型模式:适配器模式 --- 类适配器
* Usb接口,Ps2接口,无法直接互相使用,因此通过类适配器的方式,进行适配
* 文章参考:https://www.cnblogs.com/V1haoge/p/6479118.html
*
* 类适配器与对象适配器的使用场景一致,仅仅是实现手段稍有区别,二者主要用于如下场景:
* (1)想要使用一个已经存在的类,但是它却不符合现有的接口规范,导致无法直接去访问,这时创建一个适配器就能间接去访问这个类中的方法
* (2)我们有一个类,想将其设计为可重用的类(可被多处访问),我们可以创建适配器来将这个类来适配其他没有提供合适接口的类
* @author kxm
*/
public class TestAdapter {
public static void main(String[] args) {
Ps2 ps2 = new UsbAdapter();
ps2.isPs2();
}
}
测试结果:
USB口 ---- 可以发现,我们调用的是Ps2的接口方法,返回的是Usb口,达到了适配的目的
实例讲解:
我手中有个ps2插头的设备,但是主机上只有usb插头的插口,怎么办呢?弄个转换器,将ps2插头转换成为USB插头就可以使用了。
接口Ps2:描述ps2接口格式
接口Usb:描述USB接口格式
类Usber:是接口Usb的实现类,是具体的USB接口格式
Adapter:用于将ps2接口格式转换成为USB接口格式
2、对象适配器模式
原理:通过组合来实现适配器功能。
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后在适配器P中定义私有变量C(对象)(B接口指向变量名),再定义一个带参数的构造器用来为对象C赋值,再在A接口的方法实现中使用对象C调用其来源于B接口的方法。
详见下方实例:我们仍然以ps2与usb的转接为例
ps2接口:Ps2
public interface Ps2 {
void isPs2();
}
USB接口:Usb
public interface Usb {
void isUsb();
}
USB接口实现类:Usber
public class Usber implements Usb {
@Override
public void isUsb() {
System.out.println("USB口");
}
}
适配器:Adapter
public class Adapter implements Ps2 {
private Usb usb;
public Adapter(Usb usb){
this.usb = usb;
}
@Override
public void isPs2() {
usb.isUsb();
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
Ps2 p = new Adapter(new Usber());
p.isPs2();
}
}
测试结果:
USB口
实例讲解:
我手中有个ps2插头的设备,但是主机上只有usb插头的插口,怎么办呢?弄个转换器,将ps2插头转换成为USB插头就可以使用了。
接口Ps2:描述ps2接口格式
接口Usb:描述USB接口格式
类Usber:是接口Usb的实现类,是具体的USB接口格式
Adapter:用于将ps2接口格式转换成为USB接口格式
3、接口适配器模式
原理:通过抽象类来实现适配,这种适配稍别于上面所述的适配。
当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法,如果我们直接实现接口,那么我们要对所有的方法进行实现,哪怕我们仅仅是对不需要的方法进行置空(只写一对大括号,不做具体方法实现)也会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
目标接口:A
public interface A {
void a();
void b();
void c();
void d();
void e();
void f();
}
适配器:Adapter
public abstract class Adapter implements A {
public void a(){}
public void b(){}
public void c(){}
public void d(){}
public void e(){}
public void f(){}
}
实现类:Ashili
public class Ashili extends Adapter {
public void a(){
System.out.println("实现A方法被调用");
}
public void d(){
System.out.println("实现d方法被调用");
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
A a = new Ashili();
a.a();
a.d();
}
}
4、适配器模式应用场景
类适配器与对象适配器的使用场景一致,仅仅是实现手段稍有区别,二者主要用于如下场景:
(1)想要使用一个已经存在的类,但是它却不符合现有的接口规范,导致无法直接去访问,这时创建一个适配器就能间接去访问这个类中的方法。
(2)我们有一个类,想将其设计为可重用的类(可被多处访问),我们可以创建适配器来将这个类来适配其他没有提供合适接口的类。
以上两个场景其实就是从两个角度来描述一类问题,那就是要访问的方法不在合适的接口里,一个从接口出发(被访问),一个从访问出发(主动访问)。
接口适配器使用场景:
(1)想要使用接口中的某个或某些方法,但是接口中有太多方法,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口,并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器。
(2)装饰器模式
一、概述
装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能。
不同于适配器模式和桥接模式,装饰器模式涉及的是单方,和代理模式相同,而且目标必须是抽象的。
而实际上,装饰器模式和代理模式的实现方式基本一致,只在目标的存在上有些差别,这个后面我们具体讲述。
二、初步分析
上面提到了两点:
(1)涉及的是单方
(2)目标是抽象的
我们来想一下,所谓单方主要指的是在整个装饰器模式中不存在双方调用,要解决的也不是双方调用的问题,而是解决单方提供对外服务的问题,这个单方在自行对外提供服务时,功能不足,或者我们需要额外添加一些新功能,这时就可以使用装饰器模式,来对这个单方进行增强。
目标抽象的意思是因为我们需要通过实现接口的方式来进行增强,因此目标必须抽象为接口。
三、实例
下面我们用我们生活中的一个例子来说明,我们用房子来作为目标:
房子接口:House
/**
* 目标接口:房子
*/
public interface House {
void output();
}
具体的房子:DonghaoHouse
/**
* 房子实现类
*/
public class DonghaoHouse implements House {
@Override
public void output() {
System.out.println("这是董浩的房子");
}
}
具体的房子:DongliangHouse
/**
* 房子实现类
*/
public class DongliangHouse implements House {
@Override
public void output() {
System.out.println("这是董量的房子");
}
}
装饰器:Decorator
public class Decorator implements House {
private House house;
public Decorator(House house){
this.house = house;
}
@Override
public void output() {
System.out.println("这是针对房子的前段装饰增强");
house.output();
System.out.println("这是针对房子的后段装饰增强");
}
}
测试类:
public class Clienter {
public static void main(String[] args) {
House donghaoHouse = new DonghaoHouse();
House decorator = new Decorator(donghaoHouse);
decorator.output();
}
}
执行结果为:
这是针对房子的前段装饰增强
这是董浩的房子
这是针对房子的后段装饰增强
四、解析
通过上面的例子我们可以看出,除了测试类外,只剩下接口和实现类了,即使是装饰器类也是目标接口的一个字类,这更能说明单方的说法,模式中所有的类都属于目标方。至于目标是抽象的更是如此,只有目标是抽象的,才可以使用装饰器模式来进行增强。
上面我们说过装饰器模式与代理模式基本相同,只存在少许差别。
我们需要从概念上了解代理和装饰的区别:
(1)代理是全权代理,目标根本不对外,全部由代理类来完成。
(2)装饰是增强,是辅助,目标仍然可以自行对外提供服务,装饰器只起增强作用。
上面两点提现到代码实现中是这样的:
代理模式
public class Proxy implements House {
private House house;
public Decorator(){
this.house = new DonghaoHouse();
}
@Override
public void output() {
System.out.println("这是针对目标的前段增强");
house.output();
System.out.println("这是针对目标的后段增强");
}
}
装饰模式
public class Decorator implements House {
private House house;
public Decorator(House house){
this.house = house;
}
@Override
public void output() {
System.out.println("这是针对房子的前段装饰增强");
house.output();
System.out.println("这是针对房子的后段装饰增强");
}
}
看出来了吗,装饰器中持有的目标实例是从构造器传入的,而代理中持有的目标实例是自己创建的。
那么这里又出现一个区别,代理模式和装饰器模式虽然都依赖于目标接口,但是代理针对的目标实现类是固定的,而装饰器模式可以随意指定,也就是说目标是可以自有扩展的。
五、使用场景
装饰器模式就是使用在对已有的目标功能存在不足,需要增强时,前提是目标存在抽象接口。
六、总结
我们要明白代理模式和装饰器模式的区别,区分二者的使用场景,如下图:
(3)代理模式
代理就是中介,中间人。法律上也有代理,比如代理律师之类,委托人将自己的一部分权限委托给代理者,代理者就拥有被代理者(委托人)的部分权限,并且可以以被代理人的名义来实行这些权限,此时代理者与委托人等同,当然代理人也可以在实行权限时配合自己的能力来进行,当然不能超出这个权限。
Java中的代理模式类似于上面的代理,我们也是为一个类(委托类)创建一个代理类,来代表它来对外提供功能。
如何在Java中创建一个类的代理类呢?
很简单,我们需要创建一个公共接口,委托类要实现这个接口,再创建一个接口的实现类作为代理类,在这个类中的方法中可以直接调用委托类中的同名方法,外部类要进行访问时,可以使用接口指向代理类实例,调用代理类中的方法,从而间接调用委托类中的具体方法实现。
我们就以法律上的委托代理为例来写个实例:
总接口:ZiRanRen
public interface ZiRanRen {
void Quanli();
}
委托人:MaYun
public class MaYun implements ZiRanRen {
public void eat() {
System.out.println("今天吃满汉全席");
}
public void drink() {
System.out.println("今天喝大西洋");
}
@Override
public void Quanli() {
System.out.println("我赋予我的代理律师来行使这些权利,此时代理律师全权代理我处理某些事务");
}
}
代理律师:LvShi
public class LvShi implements ZiRanRen {
@Override
public void Quanli() {
new MaYun().Quanli();
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
ZiRanRen ls = new LvShi();
ls.Quanli();
}
}
执行结果:
我赋予我的代理律师来行使这些权利,此时代理律师全权代理我处理某些事务
上面是一个很简单的例子,可以看出,我们想对外开放某些功能,就可以将这些功能在代理类中被引用,如此一来,屏蔽了我们不想外露的功能,只将我们想开放的功能开放出来。亦即委托类中其实是可以有很多方法的,很多功能的,我们可以酌情对外开放,代理类犹如一道大门,将委托类与外部调用者隔绝开来,只将部分功能赋予这个大门,来代替委托类行使这个功能,哪怕最终还是要牵扯到自身(因为最终还是要调用委托类的对应方法实现)。
代理模式很简单,只要记住以下关键点,简单易实现:
(1)代理类与委托类实现同一接口
(2)在委托类中实现功能,在代理类的方法中中引用委托类的同名方法
(3)外部类调用委托类某个方法时,直接以接口指向代理类的实例,这正是代理的意义所在:屏蔽。
代理模式场景描述:
(1)当我们想要隐藏某个类时,可以为其提供代理类
(2)当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中金进行权限判断来进行不同权限的功能调用)
(3)当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行)
代理模式虽然实现了调用者与委托类之间的强耦合,但是却增加了代理类与委托类之间的强耦合(在代理类中显式调用委托类的方法),而且增加代理类之后明显会增加处理时间,拖慢处理时间。
第二种讲解
代理模式分为静态代理,JDK动态代理,CGLIB动态代理,三种方式,代理模式是Spring中面向切面编程的核心
静态代理:
// 定义普通接口
public interface Subject {
public void shopping();
}
// 普通实现类实现接口
public class SuperMan implements Subject {
@Override
public void shopping() {
System.out.println("超人要去购物了~~~");
}
}
// 代理类 --- 代理SuperMan这个类,增强其方法,控制其访问
public class Proxy implements Subject {
private SuperMan superman;
public Proxy(SuperMan superMan) {
this.superman = superMan;
}
@Override
public void shopping() {
//代购之前要做的事情
System.out.println("做大量的商品专业评估");
System.out.println("==========代理之前==========");
superman.shopping();//被代理人真正的业务
System.out.println("==========代理之后==========");
//代购之后要做的事情
System.out.println("代购之后的客户满意度调查");
}
}
// 测试类
/***
* 结构型模式: 代理模式 --- 静态代理
* 主要的思路就是把,继承同一的接口的类A,放到继承接口的类B中调用,在调用前后可以加上新的方法
*
* Java中线程的设计就使用了静态代理设计模式,其中自定义线程类实现Runable接口,
* Thread类也实现了Runalbe接口,在创建子线程的时候,
* 传入了自定义线程类的引用,再通过调用start()方法,调用自定义线程对象的run()方法。实现了线程的并发执行。
*
* @author kxm
*/
public class TestProxy {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Subject subject = new Proxy(superMan);
subject.shopping();
}
}
测试结果:
做大量的商品专业评估
==========方法调用之前==========
超人要去购物了~~~
==========方法调用之后==========
代购之后的客户满意度调查
JDK动态代理:
// 普通接口
public interface UserService {
void saveUser();
}
// 普通实现
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("调用 saveUser() 方法");
}
}
// 代理类 --- 进行JDK动态代理 注意Proxy.newProxyInstance()方法的参数
// 参数是:被代理的类加载器,接口,重写InvocationHandler方法
public class MyProxyUtil {
public static UserService getProxyByJDK(UserService service) {
UserService userService = (UserService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("记录日志-开始");
Object obj = method.invoke(service, args);
System.out.println("记录日志-结束");
return obj;
}
});
return userService;
}
}
// 测试类
/***
* 通过JDK动态代理技术,还有一种是CGLIB动态代理,详情见Spring中的代码
* @author kxm
*/
public class Test {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();
// 生成代理对象
UserService proxy = MyProxyUtil.getProxyByJDK(userService);
// 调用目标对象方法
userService.saveUser();
System.out.println("===================================");
// 调用代理对象方法
proxy.saveUser();
}
}
测试结果:
调用 saveUser() 方法
===================================
记录日志-开始
调用 saveUser() 方法
记录日志-结束
(4)外观模式
1、外观模式简介
外观模式,一般用在子系统与访问之间,用于对访问屏蔽复杂的子系统调用,采用耳目一新的外观类提供的简单的调用方法,具体的实现由外观类去子系统调用。
外观模式任然是一种中间件类型的模式,使用外观模式之后子系统的方法调用并非完全屏蔽,只是为访问者提供了一种更佳的访问方式,如果你不嫌麻烦,任然可以直接进行子系统方法调用。
甚至于在子系统与子系统之间进行调用时也可以通过各自的外观类来进行调用,这样代码方便管理。
下面请看代码实例:更能显示这种情况
子系统方法1
1 public class SubMethod1 {
2 public void method1(){
3 System.out.println("子系统中类1的方法1");
4 }
5 }
子系统方法2
1 public class SubMethod2 {
2 public void method2(){
3 System.out.println("子系统中类2方法2");
4 }
5 }
子系统方法3
1 public class SubMethod3 {
2 public void method3(){
3 System.out.println("子系统类3方法3");
4 }
5 }
外观类:
public class Facader {
private SubMethod1 sm1 = new SubMethod1();
private SubMethod2 sm2 = new SubMethod2();
private SubMethod3 sm3 = new SubMethod3();
public void facMethod1(){
sm1.method1();
sm2.method2();
}
public void facMethod2(){
sm2.method2();
sm3.method3();
sm1.method1();
}
}
测试类:
public class Clienter {
public static void main(String[] args) {
Facader face = new Facader();
face.facMethod1();
// face.facMethod2();
}
}
其实直接调用也会得到相同的结果,但是采用外观模式能规范代码,外观类就是子系统对外的一个总接口,我们要访问子系统是,直接去子系统对应的外观类进行访问即可!
2、外观模式应用场景
当我们访问的子系统拥有复杂额结构,内部调用繁杂,初接触者根本无从下手时,不凡由资深者为这个子系统设计一个外观类来供访问者使用,统一访问路径(集中到外观类中),将繁杂的调用结合起来形成一个总调用写到外观类中,之后访问者不用再繁杂的方法中寻找需要的方法进行调用,直接在外观类中找对应的方法进行调用即可。
还有就是在系统与系统之间发生调用时,也可以为被调用子系统设计外观类,这样方便调用也,屏蔽了系统的复杂性。
(5)桥接模式
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
个人理解:桥接是一个接口,它与一方应该是绑定的,也就是解耦的双方中的一方必然是继承这个接口的,这一方就是实现方,而另一方正是要与这一方解耦的抽象方,如果不采用桥接模式,一般我们的处理方式是直接使用继承来实现,这样双方之间处于强链接,类之间关联性极强,如要进行扩展,必然导致类结构急剧膨胀。采用桥接模式,正是为了避免这一情况的发生,将一方与桥绑定,即实现桥接口,另一方在抽象类中调用桥接口(指向的实现类),这样桥方可以通过实现桥接口进行单方面扩展,而另一方可以继承抽象类而单方面扩展,而之间的调用就从桥接口来作为突破口,不会受到双方扩展的任何影响。
下面的实例能真正体现着一点:
实例准备:我们假设有一座桥,桥左边为A,桥右边为B,A有A1,A2,A3等,表示桥左边的三个不同地方,B有B1,B2,B3等,表示桥右边的三个不同地方,假设我们要从桥左侧A出发到桥的右侧B,我们可以有多重方案,A1到B1,A1到B2,A1到B3,A2到B1…等等,以此为例,代码如下:
桥接口:Qiao
1 public interface Qiao {
2 //目的地B
3 void targetAreaB();
4 }
目的地B1,B2,B3:
/**
* 目的地B1
*/
public class AreaB1 implements Qiao {
@Override
public void targetAreaB() {
System.out.println("我要去B1");
}
}
/**
* 目的地B2
*/
public class AreaB2 implements Qiao {
@Override
public void targetAreaB() {
System.out.println("我要去B2");
}
}
/**
* 目的地B3
*/
public class AreaB3 implements Qiao {
@Override
public void targetAreaB() {
System.out.println("我要去B3");
}
}
抽象来源地A:AreaA
public abstract class AreaA {
//引用桥接口
Qiao qiao;
//来源地
abstract void fromAreaA();
}
来源地A1,A2,A3:
/**
* 来源地A1
*/
public class AreaA1 extends AreaA {
@Override
void fromAreaA() {
System.out.println("我来自A1");
}
}
/**
* 来源地A2
*/
public class AreaA2 extends AreaA {
@Override
void fromAreaA() {
System.out.println("我来自A2");
}
}
/**
* 来源地A3
*/
public class AreaA3 extends AreaA {
@Override
void fromAreaA() {
System.out.println("我来自A3");
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
AreaA a = new AreaA2();
a.qiao = new AreaB3();
a.fromAreaA();
a.qiao.targetAreaB();
}
}
运行结果:
我来自A2
我要去B3
如何,只要你认真看完了实例,你就明白了这种模式的好处,现在我们要添加来源地和目的地,只要继续继承AreaA和实现Qiao即可,之前我所说的绑定,正式此处将桥与目的地绑定在一起,使用一个接口完成。
其实要完成桥接模式,注意点并不多,重在理解模式的使用场景。
注意点:
1、定义一个桥接口,使其与一方绑定,这一方的扩展全部使用实现桥接口的方式。
2、定义一个抽象类,来表示另一方,在这个抽象类内部要引入桥接口,而这一方的扩展全部使用继承该抽象类的方式。
其实我们可以发现桥接模式应对的场景有方向性的,桥绑定的一方都是被调用者,属于被动方,抽象方属于主动方。
其实我的JDK提供的JDBC数据库访问接口API正是经典的桥接模式的实现者,接口内部可以通过实现接口来扩展针对不同数据库的具体实现来进行扩展,而对外的仅仅只是一个统一的接口调用,调用方过于抽象,可以将其看做每一个JDBC调用程序(这是真实实物,当然不存在抽象)
下面来理解一下开头的概念:
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
理解:此处抽象化与实现化分别指代实例中的双方,而且实现化对应目的地方(通过实现桥接口进行扩展),抽象方对应来源地方(通过继承抽象类来进行扩展),如果我们不使用桥接模式,我们会怎么想实现这个实例呢?很简单,我们分别定义来源地A1、A2、A3类和目的地B1、B2、B3,然后具体的实现就是,A1到B1一个类,A1到B2一个类,等,如果我们要扩展了A和B ,要直接增加An类和Bn类,如此编写不说类内部重复性代码多,而且还会导致类结构的急剧膨胀,最重要的是,在通过继承实现路径的时候,会造成双方耦合性增大,而这又进一步加剧了扩展的复杂性。使用桥结构模式可以很好地规避这些问题:重在解耦。
(6)组合模式
组合模式是将存在某种包含关系的数据组织在一起,典型的例子就是树状结构。例如菜单功能,一个菜单除了自己该有的属性,还可能包含子菜单,创建的时候可以使用递归的方法。
(7)享元模式
享元模式:“享”就是分享之意,指一物被众人共享,而这也正是该模式的终旨所在。
享元模式有点类似于单例模式,都是只生成一个对象来被共享使用。这里有个问题,那就是对共享对象的修改,为了避免出现这种情况,我们将这些对象的公共部分,或者说是不变化的部分抽取出来形成一个对象。这个对象就可以避免到修改的问题。
享元的目的是为了减少不会要额内存消耗,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,以此来降低内存的消耗。
下面我们来看一个简单的例子:
建筑接口:JianZhu
1 public interface Jianzhu {
2 void use();
3 }
体育馆实现类:TiYuGuan
public class TiYuGuan implements Jianzhu {
private String name;
private String shape;
private String yundong;
public TiYuGuan(String yundong){
this.setYundong(yundong);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getShape() {
return shape;
}
public void setShape(String shape) {
this.shape = shape;
}
public String getYundong() {
return yundong;
}
public void setYundong(String yundong) {
this.yundong = yundong;
}
@Override
public void use() {
System.out.println("该体育馆被使用来召开奥运会" + " 运动为:"+ yundong+" 形状为:"+shape+ " 名称为:"+name);
}
}
建筑工厂类:JianZhuFactory
import java.util.*;
public class JianZhuFactory {
private static final Map<String,TiYuGuan> tygs = new HashMap<String,TiYuGuan>();
public static TiYuGuan getTyg(String yundong){
TiYuGuan tyg = tygs.get(yundong);
if(tyg == null){
tyg = new TiYuGuan(yundong);
tygs.put(yundong,tyg);
}
return tyg;
}
public static int getSize(){
return tygs.size();
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
String yundong ="足球";
for(int i = 1;i <= 5;i++){
TiYuGuan tyg = JianZhuFactory.getTyg(yundong);
tyg.setName("中国体育馆");
tyg.setShape("圆形");
tyg.use();
System.out.println("对象池中对象数量为:"+JianZhuFactory.getSize());
}
}
}
执行结果:
该体育馆被使用来召开奥运会 运动为:足球 形状为:圆形 名称为:中国体育馆
对象池中对象数量为:1
该体育馆被使用来召开奥运会 运动为:足球 形状为:圆形 名称为:中国体育馆
对象池中对象数量为:1
该体育馆被使用来召开奥运会 运动为:足球 形状为:圆形 名称为:中国体育馆
对象池中对象数量为:1
该体育馆被使用来召开奥运会 运动为:足球 形状为:圆形 名称为:中国体育馆
对象池中对象数量为:1
该体育馆被使用来召开奥运会 运动为:足球 形状为:圆形 名称为:中国体育馆
对象池中对象数量为:1
如上示例中,使用工厂模式进行配合,创建对象池,测试类中的循环,你可以想象成为要举行5场比赛,每场比赛的场地就是体育馆
通过执行结果可以看出,在这个对象池(HashMap)中,一直都只有一个对象存在,第一次使用的时候创建对象,之后的每次调用都用的是那个对象,不会再重新创建。
其实在Java中就存在这种类型的实例:String。
Java中将String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,这个字符串常量池在jdk 6.0以前是位于常量池中,位于永久代,而在JDK 7.0中,JVM将其从永久代拿出来放置于堆中。
我们使用如下代码定义的两个字符串指向的其实是同一个字符串常量池中的字符串值。
String s1 = "abc";
String s2 = "abc";
如果我们以s1==s2进行比较的话所得结果为:true,因为s1和s2保存的是字符串常量池中的同一个字符串地址。这就类似于我们今天所讲述的享元模式,字符串一旦定义之后就可以被共享使用,因为他们是不可改变的,同时被多处调用也不会存在任何隐患。
享元模式使用的场景:
当我们项目中创建很多对象,而且这些对象存在许多相同模块,这时,我们可以将这些相同的模块提取出来采用享元模式生成单一对象,再使用这个对象与之前的诸多对象进行配合使用,这样无疑会节省很多空间。
3.行为型模式
(1)策略模式
将算法的责任和本身进行解耦,使得:
-
算法可独立于使用外部而变化
-
客户端方便根据外部条件选择不同策略来解决不同问题
我们举一个销售策略的例子,在不同的时节,需要使用不同的销售方式,因此定义如下:
// 定义接口方法
public abstract class Strategy {
public abstract void show();
}
//为春节准备的促销活动A
class StrategyA extends Strategy{
@Override
public void show() {
System.out.println("为春节准备的促销活动A");
}
}
//为中秋节准备的促销活动B
class StrategyB extends Strategy{
@Override
public void show() {
System.out.println("为中秋节准备的促销活动B");
}
}
//为圣诞节准备的促销活动C
class StrategyC extends Strategy{
@Override
public void show() {
System.out.println("为圣诞节准备的促销活动C");
}
}
定义销售人员选择策略:
public class SalesMan {
//持有抽象策略角色的引用
private Strategy strategy;
//生成销售员实例时告诉销售员什么节日(构造方法)
//使得让销售员根据传入的参数(节日)选择促销活动(这里使用一个简单的工厂模式)
public SalesMan(String festival) {
switch ( festival) {
//春节就使用春节促销活动
case "A":
strategy = new StrategyA();
break;
//中秋节就使用中秋节促销活动
case "B":
strategy = new StrategyB();
break;
//圣诞节就使用圣诞节促销活动
case "C":
strategy = new StrategyC();
break;
}
}
//向客户展示促销活动
public void SalesManShow(){
strategy.show();
}
}
测试类展示效果:
/***
* 行为型模式:策略模式
* 定义一系列算法,将每个算法封装到具有公共接口的一系列策略类中,
* 从而使它们可以相互替换 & 让算法可在不影响客户端的情况下发生变化
* 优点:
* 策略类之间可以自由切换
* 易于扩展
* 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
*
* 参考文档:https://www.jianshu.com/p/0c62bf587b9c
* @author kxm
*/
public class StrategyPattern {
public static void main(String[] args) {
SalesMan mSalesMan ;
//春节来了,使用春节促销活动
System.out.println("对于春节:");
mSalesMan = new SalesMan("A");
mSalesMan.SalesManShow();
//中秋节来了,使用中秋节促销活动
System.out.println("对于中秋节:");
mSalesMan = new SalesMan("B");
mSalesMan.SalesManShow();
//圣诞节来了,使用圣诞节促销活动
System.out.println("对于圣诞节:");
mSalesMan = new SalesMan("C");
mSalesMan.SalesManShow();
}
}
测试结果:
对于春节:
为春节准备的促销活动A
对于中秋节:
为中秋节准备的促销活动B
对于圣诞节:
为圣诞节准备的促销活动C
(2)模板方法模式
模板模式,顾名思义,就是通过模板拓印的方式。
定义模板,就是定义框架、结构、原型。定义一个我们共同遵守的约定。
定义了模板,我们的剩余工作就是对其进行充实、丰润,完善它的不足之处。
定义模板采用抽象类来定义,公共的结构化逻辑需要在抽象类中完成,只将非公共的部分逻辑抽象成抽象方法,留待子类充实实现。
所以上文所述不足之处就是这些抽象方法。
总的来说,模板模式就是通过抽象类来定义一个逻辑模板,逻辑框架、逻辑原型,然后将无法决定的部分抽象成抽象类交由子类来实现,一般这些抽象类的调用逻辑还是在抽象类中完成的。这么看来,模板就是定义一个框架,比如盖房子,我们定义一个模板:房子要封闭,有门,有窗等等,但是要什么样的门,什么样的窗,这些并不在模板中描述,这个交给子类来完善,比如门使用防盗门,窗使用北向的窗等等。
我们不凡就以建房为例来见识一下模板模式如何:
模板抽象类:HouseTemplate
public abstract class HouseTemplate {
protected HouseTemplate(String name){
this.name = name;
}
protected String name;
protected abstract void buildDoor();
protected abstract void buildWindow();
protected abstract void buildWall();
protected abstract void buildBase();
//公共逻辑
public final void buildHouse(){
buildBase();
buildWall();
buildDoor();
buildWindow();
}
}
子类1:HouseOne
public class HouseOne extends HouseTemplate {
HouseOne(String name){
super(name);
}
@Override
protected void buildDoor() {
System.out.println(name +"的门要采用防盗门");
}
@Override
protected void buildWindow() {
System.out.println(name + "的窗户要面向北方");
}
@Override
protected void buildWall() {
System.out.println(name + "的墙使用大理石建造");
}
@Override
protected void buildBase() {
System.out.println(name + "的地基使用钢铁地基");
}
}
子类2:HouseTwo
public class HouseTwo extends HouseTemplate {
HouseTwo(String name){
super(name);
}
@Override
protected void buildDoor() {
System.out.println(name + "的门采用木门");
}
@Override
protected void buildWindow() {
System.out.println(name + "的窗户要向南");
}
@Override
protected void buildWall() {
System.out.println(name + "的墙使用玻璃制造");
}
@Override
protected void buildBase() {
System.out.println(name + "的地基使用花岗岩");
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args){
HouseTemplate houseOne = new HouseOne("房子1");
HouseTemplate houseTwo = new HouseTwo("房子2");
houseOne.buildHouse();
houseTwo.buildHouse();
}
}
测试结果:
房子1的地基使用钢铁地基
房子1的墙使用大理石建造
房子1的门要采用防盗门
房子1的窗户要面向北方
房子2的地基使用花岗岩
房子2的墙使用玻璃制造
房子2的门采用木门
房子2的窗户要向南
通过以上例子,我们认识了模板模式中的基本方法和模板方法,其中HouseTemplate中的buildHouse方法就是基本方法,其余四个均为模板方法。其中基本方法一般会用final修饰,保证其不会被子类修改,而模板方法则使用protected修饰,表明其需要在子类中实现。
(3)观察者模式
观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监听者,类似监听器的存在,一旦被观察/监听的目标发生的情况,就会被监听者发现,这么想来目标发生情况到观察者知道情况,其实是由目标将情况发送到观察者的。
观察者模式多用于实现订阅功能的场景,例如微博的订阅,当我们订阅了某个人的微博账号,当这个人发布了新的消息,就会通知我们。
现在我们举一个类似的情况,并使用代码来实现,为大家提供一个比较明显的认识。
警察在找到嫌犯的时候,为了找到幕后主使,一般都会蹲点监察,这里我有三名便衣警察来蹲点监察2名嫌犯,三名便衣分别是:张昊天、石破天、赵日天,两名嫌犯是:大熊与黑狗,详见代码:
观察者接口:Observer
1 public interface Observer {
2 void update(String message,String name);
3 }
定义三名便衣观察者:Bianyi1、Bianyi2、Bianyi3
/**
* 便衣警察张昊天
*/
public class Bianyi1 implements Observer {
//定义姓名
private String bname = "张昊天";
@Override
public void update(String message,String name) {
System.out.println(bname+":"+name+"那里有新情况:"+ message);
}
}
/**
* 便衣警察石破天
*/
public class Bianyi2 implements Observer {
//定义姓名
private String bname = "石破天";
@Override
public void update(String message,String name) {
System.out.println(bname+":"+name+"那里有新情况:"+ message);
}
}
/**
* 便衣警察赵日天
*/
public class Bianyi3 implements Observer {
//定义姓名
private String bname = "赵日天";
@Override
public void update(String message,String name) {
System.out.println(bname+":"+name+"那里有新情况:"+ message);
}
}
目标接口:Huairen
public interface Huairen {
//添加便衣观察者
void addObserver(Observer observer);
//移除便衣观察者
void removeObserver(Observer observer);
//通知观察者
void notice(String message);
}
定义两个嫌疑犯:XianFan1、XianFan2
import java.util.*;
/**
* 嫌犯大熊
*/
public class XianFan1 implements Huairen {
//别称
private String name = "大熊";
//定义观察者集合
private List<Observer> observerList = new ArrayList<Observer>();
//增加观察者
@Override
public void addObserver(Observer observer) {
if(!observerList.contains(observer)){
observerList.add(observer);
}
}
//移除观察者
@Override
public void removeObserver(Observer observer) {
if(observerList.contains(observer)){
observerList.remove(observer);
}
}
//通知观察者
@Override
public void notice(String message) {
for(Observer observer:observerList){
observer.update(message,name);
}
}
}
import java.util.*;
/**
* 嫌犯黑狗
*/
public class XianFan2 implements Huairen {
//别称
private String name = "黑狗";
//定义观察者集合
private List<Observer> observerList = new ArrayList<Observer>();
//增加观察者
@Override
public void addObserver(Observer observer) {
if(!observerList.contains(observer)){
observerList.add(observer);
}
}
//移除观察者
@Override
public void removeObserver(Observer observer) {
if(observerList.contains(observer)){
observerList.remove(observer);
}
}
//通知观察者
@Override
public void notice(String message) {
for(Observer observer:observerList){
observer.update(message,name);
}
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
//定义两个嫌犯
Huairen xf1 = new XianFan1();
Huairen xf2 = new XianFan2();
//定义三个观察便衣警察
Observer o1 = new Bianyi1();
Observer o2 = new Bianyi2();
Observer o3 = new Bianyi3();
//为嫌犯增加观察便衣
xf1.addObserver(o1);
xf1.addObserver(o2);
xf2.addObserver(o1);
xf2.addObserver(o3);
//定义嫌犯1的情况
String message1 = "又卖了一批货";
String message2 = "老大要下来视察了";
xf1.notice(message1);
xf2.notice(message2);
}
}
测试结果:
张昊天:大熊那里有新情况:又卖了一批货
石破天:大熊那里有新情况:又卖了一批货
张昊天:黑狗那里有新情况:老大要下来视察了
包拯:黑狗那里有新情况:老大要下来视察了
通过上面的实例可以很明显的看出,观察者模式的大概模型,关键是什么呢?
关键点:
1、针对观察者与被观察者分别定义接口,有利于分别进行扩展。
2、重点就在被观察者的实现中:
(1)定义观察者集合,并定义针对集合的添加、删除操作,用于增加、删除订阅者(观察者)
(2)定义通知方法,用于将新情况通知给观察者用户(订阅者用户)
3、观察者中需要有个接收被观察者通知的方法。
如此而已!
观察者模式定义的是一对多的依赖关系,一个被观察者可以拥有多个观察者,并且通过接口对观察者与被观察者进行逻辑解耦,降低二者的直接耦合。
如此这般,想了一番之后,突然发现这种模式与桥接模式有点类似的感觉。
桥接模式也是拥有双方,同样是使用接口(抽象类)的方式进行解耦,使双方能够无限扩展而互不影响,其实二者还是有者明显的区别:
1、主要就是使用场景不同,桥接模式主要用于实现抽象与实现的解耦,主要目的也正是如此,为了双方的自由扩展而进行解耦,这是一种多对多的场景。观察者模式侧重于另一方面的解耦,侧重于监听方面,侧重于一对多的情况,侧重于一方发生情况,多方能获得这个情况的场景。
2、另一方面就是编码方面的不同,在观察者模式中存在许多独有的内容,如观察者集合的操作,通知的发送与接收,而在桥接模式中只是简单的接口引用。
(4)责任链模式
职责链模式(称责任链模式)将请求的处理对象像一条长链一般组合起来,形成一条对象链。请求并不知道具体执行请求的对象是哪一个,这样就实现了请求与处理对象之间的解耦。
生活中这种情况其实很常见,公司部门之中,政府部门之中都有体现,在公司部门中,当你提交一份请求文件给你的直接上级时,你的直接上级可以处理这个文件,若他觉得自己不够资格,会将文件传递为他的直接上级,这样文件请求在这条链中传递,直到被某位感觉自己足够资格处理文件的人给处理掉为止,在政府部门也是如此。
职责链模式需要一个总接口,用来定义处理对象的公共部分(一般使用抽象类来定义),公共部分包括:一个后继处理器,设置和获取后继处理器的方法,具体的请求处理方法(这个方法需要在每个具体处理对象中实现),这里定义为抽象方法。
我们就以公司部门为例来进行实例:
领导抽象类:Lingdao
public abstract class Lingdao {
private Lingdao nextLingdao;
public Lingdao getNextLingdao() {
return nextLingdao;
}
public void setNextLingdao(Lingdao nextLingdao) {
this.nextLingdao = nextLingdao;
}
abstract void chuli(Files file);
}
领导实现类:Zongjingli、Fujingli、Bumenjingli
/**
* 总经理
*/
public class Zongjingli extends Lingdao {
private final String name = "总经理";
private final int level = 0;//最大
@Override
public void chuli(Files file) {
if(this.level > file.getLevel()){
System.out.println(name + "未处理文件《" + file.getFileName() + "》");
getNextLingdao().chuli(file);
}else{
System.out.println(name + "处理了文件《" + file.getFileName() + "》");
}
}
}
/**
* 副经理
*/
public class Fujingli extends Lingdao {
private final String name = "副经理";
private final int level = 1;
@Override
public void chuli(Files file) {
if(this.level > file.getLevel()){
System.out.println(name + "未处理文件《" + file.getFileName() + "》");
getNextLingdao().chuli(file);
}else{
System.out.println(name + "处理了文件《" + file.getFileName() + "》");
}
}
}
/**
* 部门经理
*/
public class Bumenjingli extends Lingdao{
private final String name = "部门经理";
private final int level = 2;
@Override
public void chuli(Files file) {
if(this.level > file.getLevel()){
System.out.println(name + "未处理文件《" + file.getFileName() + "》");
getNextLingdao().chuli(file);
}else{
System.out.println(name + "处理了文件《" + file.getFileName() + "》");
}
}
}
文件类:Files
public class Files {
private String fileName;
private int level;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
//定义职责链
Lingdao zongjingli = new Zongjingli();
Lingdao fujingli = new Fujingli();
Lingdao bumenjingli = new Bumenjingli();
bumenjingli.setNextLingdao(fujingli);
fujingli.setNextLingdao(zongjingli);
//定义两份文件
Files f1 = new Files();
f1.setFileName("正确对待旱鸭子");
f1.setLevel(1);
Files f2 = new Files();
f2.setFileName("年会在那里举行");
f2.setLevel(0);
//提交文件
bumenjingli.chuli(f1);
bumenjingli.chuli(f2);
}
}
执行结果:
部门经理未处理文件《正确对待旱鸭子》
副经理处理了文件《正确对待旱鸭子》
部门经理未处理文件《年会在那里举行》
副经理未处理文件《年会在那里举行》
总经理处理了文件《年会在那里举行》
实例清晰易懂,职责链的模式重在这个链的组成,如何组成链呢?
第一步需要在处理者对象类中定义后继处理者对象,将这部分代码抽象到抽象类中实现,降低了代码重复性。
第二步就是在处理者对象类中的处理方法中如果当前处理者对象无法处理,就将其传递到后继对象去处理。
第三步就是在测试类中将这些处理者类的实例串联成链。
其实这个模式有个缺陷,那就是虽然可以实现请求额度传递,但是也不保证在这里链中一定存在能处理请求的处理这类,一旦不存在,那么这个请求将一直存在与链中,如果将链设置成环形,那么这个请求将会永远在环形链中传递不休;还有一点就是由于请求的传递,请求无法立即精确的找到处理者,处理效率会降低。
(5)命令模式
命令模式,将一个请求封装成对象,使得请求发送者和请求接受者之间相互隔离,消除了两者之间的耦合。将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化
角色分配
- 抽象命令类
- 具体命令类
- 调用者(调用具体命令)
- 接收者(根据请求执行具体相关操作)
- 客户(使用)
命令模式的实现
1.抽象命令类
public abstract class Command {
protected Receiver receiver;
public Command(Receiver receiver) {
this.receiver = receiver;
}
public abstract void Execute();
}
2.具体命令类
public class ConcreteCommand extends Command {
public ConcreteCommand(Receiver receiver) {
super(receiver);
}
public void Execute() {
receiver.Action();
}
}
3.调用者
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void ExecuteCommand() {
command.Execute();
}
}
4.接收者,接受到请求知道如何实施与执行一个与请求相关的操作
public class Receiver {
public void Action() {
System.out.println("执行请求");
}
}
5.使用
Receiver receiver = new Receiver();//将请求封装成一个对象
Command cmd = new ConcreteCommand(receiver);
Invoker invoker = new Invoker(cmd);
invoker.ExecuteCommand();
(6)迭代器模式
迭代器模式,提供一种方法顺序的访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
优点:
支持不同方式遍历聚合对象,且可以有多个遍历
迭代器简化了聚合类,增加新的聚合类与迭代器无需修改原代码
缺点:
增加新的聚合类需要增加对应的迭代器类,类的个数增加,系统变复杂
角色分配
抽象聚合类、抽象迭代器
具体聚合类、具体迭代器(在具体聚合类中实现)
客户端(使用)
迭代器模式的实现
1.抽象聚合类
public interface Television{
public TVIterator createIterator();
}
2.抽象迭代器
public interface TVIterator {
public void setChannel(int i);//设置初始频道
public void next();//下一个频道
public boolean isLast();//是否为最后一个频道
}
3.具体聚合类与具体迭代器
public class TCLTelevision implements Television {
private Object[] obj = {"CCTV1","CCTV2","CCTV3","CCTV4","CCTV5","CCTV6","CCTV7","CCTV8","CCTV9","CCTV10","CCTV11","CCTV12","CCTV13","CCTVnews"};
public TVIterator createIterator() {
return new TCLIterator();
}
//具体迭代器
class TCLIterator implements TVIterator{
//当前频道
private int currentIndex = 0;
public void setChannel(int i) {
this.currentIndex = i;
}
public void next() {
if(currentIndex<obj.length){
currentIndex++;
}
}
public boolean isLast() {
if(currentIndex<14){
return false;
} else {
return true;
}
}
}
}
4.客户端(使用)
public class Client {
public static void display(Television tv){
TVIterator tvIterator = tv.createIterator();
System.out.println("正向输出节目表:");
while(!tvIterator.isLast()){
System.out.println(tvIterator.currentChannel().toString());
tvIterator.next();
}
}
public static void main(String[] args){
Television tv;
tv = new TCLTelevision();
display(tv);
System.out.println("-----------------------------");
reverseDisplay(tv);
}
}