目录
一、软件设计模式的概念
软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。
二、设计模式的分类
创建型模式
用于描述“怎样创建对象”,它的主要特点是“==将对象的创建与使用分离==”。GoF(四人组)书中提供了==单例、原型、工厂方法、抽象工厂、建造者==等 5 种创建型模式。
结构型模式
用于描述如何将类或对象按某种布局组成更大的结构,GoF(四人组)书中提供了==代理、适配器、桥接、装饰、外观、享元、组合==等 7 种结构型模式。
行为型模式
用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了==模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器==等 11 种行为型模式。
总共有23种设计模式 我们一般只会用7种设计模式。
单例模式 工厂模式 代理模式 适配器模式 观察者模式 策略模式 模板方法 模式
三、单例设计模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
总结: 该类只能被创建一次。而且这个创建是给类本身自己创建。
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉模式:类加载就会导致该单实例对象被创建
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/7 13:33
* @Version 1.0
*/
package com.wjk.demo1107.demo04;
public class Singleton {
private Singleton(){};
//声明一个静态的本类对象 饿汉模式
private static Singleton singleton=new Singleton();
//提供一个静态方法返回本类的对象
public static Singleton getInstance(){
return singleton;
}
public void show(){
System.out.println("**********show*********方法");
}
}
说明:该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。 所以我们可以使用懒汉模式
懒汉模式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/7 13:37
* @Version 1.0
*/
package com.wjk.demo1107.demo05;
public class Singleton {
private Singleton(){};
/**
* 声明一个静态的本类对象 这是懒汉式
*/
private static Singleton singleton;
/**
* 提供一个静态方法返回本类的对象
*/
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
public void show(){
System.out.println("****************show方法*****************");
}
}
说明:从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。
如果在多线程的情况下 可能获取的对象会被创建多次。
演示:
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
}).start();
}
我们可以对方法加锁。
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/7 13:37
* @Version 1.0
*/
package com.wjk.demo1107.demo05;
public class Singleton {
private Singleton(){};
/**
* 声明一个静态的本类对象 这是懒汉式
*/
private static Singleton singleton;
/**
* 提供一个静态方法返回本类的对象
*/
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
public void show(){
System.out.println("****************show方法*****************");
}
}
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/7 13:41
* @Version 1.0
*/
package com.wjk.demo1107.demo05;
public class Test02 {
public static void main(String[] args) {
/**
* 如果是多线程的情况下 ,获取的对象会被创建多次 我们可以使用加锁来解决这种问题
*/
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
}).start();
}
}
懒汉式---存在线程安全问题---为方法加锁
说明: 该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。
我们可以使用双重校验锁
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/7 13:47
* @Version 1.0
*/
package com.wjk.demo1107.demo06;
public class Singleton {
private Singleton(){};
/**
* 静态的本类对象
*/
private static Singleton singleton;
/**
* 静态的返回本类的对象
* 双重校验锁
* @return
*/
public static Singleton getInstance(){
//多个线程进来后发现singleton不为null,那么就不用再获取锁对象了
if (singleton==null){
synchronized (Singleton.class){
//如果对象不为null,则无需创建对象
if (singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
public void show(){
System.out.println("************show方法****************");
}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
这里是因为 singleton = new Singleton() ,它并非是一个原子操作,事实上,在 JVM 中上述语句至少做了以下这 3 件事:
第一步是给 singleton 分配内存空间;
第二步开始调用 Singleton 的构造函数等,来初始化 singleton;
第三步,将 singleton 对象指向分配的内存空间(执行完这步 singleton 就不是 null 了)。
这里需要留意一下 1-2-3 的顺序,因为存在JVM指令重排序的优化,也就是说第 2 步和第 3 步的顺序是不能保证的,最终的执行顺序,可能是 1-2-3,也有可能是 1-3-2。
如果是 1-3-2,那么在第 3 步执行完以后,singleton 就不是 null 了,可是这时第 2 步并没有执行,singleton 对象未完成初始化,它的属性的值可能不是我们所预期的值。假设此时线程 2 进入 getInstance 方法,由于 singleton 已经不是 null 了,所以会通过第一重检查并直接返回,但其实这时的 singleton 并没有完成初始化,所以使用这个实例的时候会报错,详细流程如下图所示:
要解决双重检查锁模式带来JVM指令重排问题,只需要使用
volatile
关键字,volatile
关键字可以防止JVM指令重排。
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/8 15:24
* @Version 1.0
*/
package com.wjk.demo1108.demo06;
public class Test01 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
}).start();
}
}
class Singleton{
private Singleton(){};
/**
* 静态本类对象 懒汉式
* volatile 防止JVM指令重排
*/
private volatile static Singleton singleton;
public static Singleton getInstance(){
if (singleton==null){
synchronized (Singleton.class){
if (singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
volatile的第二个作用: 保证线程之间的操作可见性。
在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前 的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就 可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数 据的不一致。 要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行 读取。
public class Test01 {
public static void main(String[] args) throws Exception{
T t=new T();
t.start();
Thread.sleep(2000);
System.out.println("主线程设置t线程的参数来止损失");
t.setFlag(false); //为flag修改为false
}
}
class T extends Thread{
//可以保证多线程之间的可见性
private volatile boolean flag=true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
System.out.println("进入run方法");
while(flag){
}
}
}
volatile保证有序性【防止jvm重排】. volatile保证线程之间的可见性
四、工厂模式
4.1 简介
在面向对象编程中,创建对象实例最常用的方式就是通过 new 操作符构造一个对象实例,但在某些情况下,new 操作符直接生成对象会存在一些问题。举例来说,对象的创建需要一系列的步骤:可能需要计算或取得对象的初始位置、选择生成哪个子对象实例、或在生成之前必须先生成一些辅助对象。 在这些情况,新对象的建立就是一个 “过程”,而不仅仅是一个操作,就像一部大机器中的一个齿轮传动。----自己在创建对象时 可能需要一系列的条件。
针对上面这种情况,我们如何轻松方便地构造对象实例,而不必关心构造对象示例的细节和复杂过程?解决方案就是使用一个工厂类来创建对象。
4.2 什么是工厂模式
工厂模式将目的将创建对象的具体过程屏蔽隔离起来,从而达到更高的灵活性,工厂模式可以分为三类:
简单工厂模式(Simple Factory)
工厂方法模式(Factory Method)
抽象工厂模式(Abstract Factory)
在了解工厂模式时,先从现实生活说起:
(1)在没有工厂的时代,如果客户需要一款宝马车,那么就需要客户去创建一款宝马车,然后拿来用。
(2)简单工厂模式:后来出现了工厂,用户不再需要去创建宝马车,由工厂进行创建,想要什么车,直接通过工厂创建就可以了。比如想要320i系列车,工厂就创建这个系列的车。
(3)工厂方法模式:为了满足客户,宝马车系列越来越多,如320i、523i等等系列,一个工厂无法创建所有的宝马系列,于是又单独分出来多个具体的工厂,每个具体工厂创建一种系列,即具体工厂类只能创建一个具体产品。但是宝马工厂还是个抽象,你需要指定某个具体的工厂才能生产车出来。
(4)抽象工厂模式:随着客户要求越来越高,宝马车必须配置空调,于是这个工厂开始生产宝马车和需要的空调。最终是客户只要对宝马的销售员说:我要523i空调车,销售员就直接给他523i空调车了。而不用自己去创建523i空调车宝马车。
4.3 简单工厂模式
简单工厂模式的核心是定义一个创建对象的接口,将对象的创建和本身的业务逻辑分离,降低系统的耦合度,使得两个修改起来相对容易些,当以后实现改变时,只需要修改工厂类即可。
如果不使用工厂,用户将自己创建宝马车,具体UML图和代码如下:
public class Test {
public static void main(String[] args) {
BMW320 bmw320=new BMW320();
BMW523 bmw523=new BMW523();
bmw320.move();
bmw523.move();
}
}
class BMW320{
public void move(){
System.out.println("bmw320在马路上奔跑");
}
}
class BMW523{
public void move(){
System.out.println("bmw523在马路上奔跑");
}
}
用户需要知道怎么创建一款车,这样子客户和车就紧密耦合在一起了,为了降低耦合,就出现了简单工厂模式,把创建宝马的操作细节都放到了工厂里,而客户直接使用工厂的创建方法,传入想要的宝马车型号就行了,而不必去知道创建的细节。
简单工厂模式的UML图:
工厂类角色: 该模式的核心,用来创建产品,含有一定的商业逻辑和判断逻辑
抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
//抽象产品角色
public abstract class BMW {
public abstract void move();
}
//具体产品角色
public class BMW320 extends BMW{
@Override
public void move() {
System.out.println("这时BMW320汽车");
}
}
//具体产品角色
public class BMW523 extends BMW {
@Override
public void move() {
System.out.println("这时523汽车");
}
}
//工厂类角色:
public class BMWFactory {
public static BMW createBMW(String type){
if(type.equals("320")){
return new BMW320();
}else if(type.equals("523")){
return new BMW523();
}else{
return null;
}
}
}
//测试
public static void main(String[] args) {
BMW bmw = BMWFactory.createBMW("523");
bmw.move();
}
简单工厂模式的优缺点:
简单工厂模式提供专门的工厂类用于创建对象,实现了对象创建和使用的职责分离,客户端不需知道所创建的具体产品类的类名以及创建过程,只需知道具体产品类所对应的参数即可,通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
但缺点在于不符合“开闭原则”,每次添加新产品就需要修改工厂类。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护,并且工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
为了解决简单工厂模式的问题,出现了工厂方法模式。
4.5 工厂方法模式
开闭原则: 允许增加新的类,不允许修改原来类的代码。
工厂方法模式将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码,在使用时,用于只需知道产品对应的具体工厂,关注具体的创建过程,甚至不需要知道具体产品类的类名,当我们选择哪个具体工厂时,就已经决定了实际创建的产品是哪个了。
但缺点在于,每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
工厂方法的 UML 结构图如下:
抽象工厂 AbstractFactory: 工厂方法模式的核心,是具体工厂角色必须实现的接口或者必须继承的父类,在 Java 中它由抽象类或者接口来实现。
具体工厂 Factory:被应用程序调用以创建具体产品的对象,含有和具体业务逻辑有关的代码 抽象产品 AbstractProduct:是具体产品继承的父类或实现的接口,在 Java 中一般有抽象类或者接口来实现。
具体产品 Product:具体工厂角色所创建的对象就是此角色的实例。
产品类:
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();
}
}
不过会有一个缺点:增加一个新的产品, 需要创建一个新产品的类,以及新产品的工厂类。 随着产品的不断增加,那么类也会成倍增加。
4.6 抽象工厂模式
在工厂方法模式中,我们使用一个工厂创建一个产品,一个具体工厂对应一个具体产品,但有时候我们需要一个工厂能够提供多个产品对象,而不是单一的对象,这个时候我们就需要使用抽象工厂模式。
在介绍抽象工厂模式前,我们先理清两个概念:
产品等级结构:产品等级结构指的是产品的继承结构,例如一个空调抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个空调抽象类和他的子类就构成了一个产品等级结构。
产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。比如,海尔工厂生产海尔空调、海尔冰箱,那么海尔空调则位于海尔族中。
产品等级结构和产品族结构示意图如下:
4.6.1 什么是抽象工厂模式
抽象工厂模式主要用于创建相关对象的家族。当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象;并且通过隔离具体类的生成,使得客户端不需要明确指定具体生成类;所有的具体工厂都实现了抽象工厂中定义的公共接口,因此只需要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
但该模式的缺点在于添加新的行为时比较麻烦,如果需要添加一个新产品族对象时,需要更改接口及其下所有子类,这必然会带来很大的麻烦。
UML结构图:
抽象工厂 AbstractFactory:定义了一个接口,这个接口包含了一组方法用来生产产品,所有的具体工厂都必须实现此接口。
具体工厂 ConcreteFactory:用于生产不同产品族,要创建一个产品,用户只需使用其中一个工厂进行获取,完全不需要实例化任何产品对象。
抽象产品 AbstractProduct:这是一个产品家族,每一个具体工厂都能够生产一整组产品。
具体产品 Product
4.6.2 代码实现
通过抽象工厂模式,我们可以实现以下的效果:比如宝马320系列使用空调型号A和发动机型号A,而宝马230系列使用空调型号B和发动机型号B,在为320系列生产相关配件时,就无需制定配件的型号,它会自动根据车型生产对应的配件型号A。
也就是说,当每个抽象产品都有多于一个的具体子类的时候(空调有型号A和B两种,发动机也有型号A和B两种),工厂角色怎么知道实例化哪一个子类呢?抽象工厂模式提供两个具体工厂角色(宝马320系列工厂和宝马230系列工厂),分别对应于这两个具体产品角色,每一个具体工厂角色只负责某一个产品角色的实例化,每一个具体工厂类只负责创建抽象产品的某一个具体子类的实例。
产品类:
//发动机以及型号
public interface Engine {}
public class EngineA implements Engine{
public EngineA(){
System.out.println("制造-->EngineA");
}
}
public class EngineB implements Engine{
public EngineB(){
System.out.println("制造-->EngineB");
}
}
//空调以及型号
public interface Aircondition {}
public class AirconditionA implements Aircondition{
public AirconditionA(){
System.out.println("制造-->AirconditionA");
}
}
public class AirconditionB implements Aircondition{
public AirconditionB(){
System.out.println("制造-->AirconditionB");
}
}
创建工厂类:
//创建工厂的接口
public interface AbstractFactory {
//制造发动机
public Engine createEngine();
//制造空调
public Aircondition createAircondition();
}
//为宝马320系列生产配件
public class FactoryBMW320 implements AbstractFactory{
@Override
public Engine createEngine() {
return new EngineA();
}
@Override
public Aircondition createAircondition() {
return new AirconditionA();
}
}
//宝马523系列
public class FactoryBMW523 implements AbstractFactory {
@Override
public Engine createEngine() {
return new EngineB();
}
@Override
public Aircondition createAircondition() {
return new AirconditionB();
}
}
测试:
public class Customer {
public static void main(String[] args){
//生产宝马320系列配件
FactoryBMW320 factoryBMW320 = new FactoryBMW320();
factoryBMW320.createEngine();
factoryBMW320.createAircondition();
//生产宝马523系列配件
FactoryBMW523 factoryBMW523 = new FactoryBMW523();
factoryBMW523.createEngine();
factoryBMW523.createAircondition();
}
}
4.6.3 工厂模式小结
工厂方法模式与抽象工厂模式的区别在于:
(1)工厂方法只有一个抽象产品类和一个抽象工厂类,但可以派生出多个具体产品类和具体工厂类,每个具体工厂类只能创建一个具体产品类的实例。
(2)抽象工厂模式拥有多个抽象产品类(产品族)和一个抽象工厂类,每个抽象产品类可以派生出多个具体产品类;抽象工厂类也可以派生出多个具体工厂类,同时每个具体工厂类可以创建多个具体产品类的实例
工厂模式: 把创建对象和使用对象进行分离。使用户无需关注对象创建的细节。
分为三类: 简单工厂模式 工厂方法模式 抽象工厂模式。spring中提供IOC控制反转: 他就使用了反射+工厂模式。
五、适配器模式
定义:适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
笔记本--转接头-网线
手机【25V】---充电头-- 电流220V
笔记本电源插头(3头)--转接头- 插排2头
二种适配器:类的适配器模式、对象的适配器模式
5.1 适配器模式之类适配器模式
实现方式:让Adapter继承Adaptee类,然后再实现Target接口,来实现适配器功能。
- Resource:适配者类,它是需要被访问的、需要被适配的组件. AC220V
- Target:目标接口,当前系统业务所使用的接口,可以是抽象类或接口. DC5V
- Adapter:适配器类,通过继承和实现目标接口,让客户端按照目标接口的方法访问适配者
- Client:客户端,适配器的使用者实例:手机充电需要将220V的交流电转化为手机锂电池需要的25V直流电。使用电源适配器,将 AC220v ——> DC5V。
代码实现:
/**
* 源角色(Adaptee):现在需要适配的接口。
*/
public class AC220 {
/**
* 输出220V交流电
*
* @return
*/
public int output220V() {
int output = 220;
return output;
}
}
/**
* 目标角色(Target):这就是所期待得到的接口。
* 注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
*/
public interface DC5 {
/**
* 输出5V直流电(期待得到的接口)
*
* @return
*/
int output5V();
}
/**
* 类适配器
* 适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
*/
public class PowerAdapter extends AC220 implements DC5 {
/**
* 输出5V直流电
*
* @return
*/
@Override
public int output5V() {
int output = output220V();
return (output / 44);
}
}
/**
* 测试类适配器
*/
public class TestClassAdapter {
public static void main(String[] args) {
DC5 dc5 = new PowerAdapter();
System.out.println("输出电流:" + dc5.output5V() + "V");
}
}
优点:由于Adapter继承了Adaptee类,所以它可以根据需求重写Adaptee类的方法,使得Adapter的灵活性增强了。
缺点:因为java单继承的缘故,Target类必须是接口,以便于Adapter去继承Adaptee并实现Target,完成适配的功能,但这样就导致了Adapter里暴露了Adaptee类的方法,使用起来的成本就增加了。
5.2 适配器模式之对象适配器模式
实现方式:让Adapter持有Adaptee类的实例,然后再实现Target接口,以这种持有对象的方式来实现适配器功能。
- `Adaptee`:适配者类,它是需要被访问的、需要被适配的组件
- `Target`:目标接口,当前系统业务所使用的接口,可以是抽象类或接口
- `Adapter`:适配器类,通过聚合和实现目标接口,让客户端按照目标接口的方法访问适配者
- `Client`:客户端,适配器的使用者实例:手机充电需要将220V的交流电转化为手机锂电池需要的5V直流电。使用电源适配器,将 AC220v ——> DC5V。
代码实现:
/**
* 源角色(Adaptee):现在需要适配的接口。
*/
public class AC220 {
/**
* 输出220V交流电
*
* @return
*/
public int output220V() {
int output = 220;
return output;
}
}
/**
* 目标角色(Target):这就是所期待得到的接口。
*/
public interface DC5 {
/**
* 输出5V直流电(期待得到的接口)
*
* @return
*/
int output5V();
}
/**
* 对象适配器
*/
public class PowerAdapter implements DC5 {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
/**
* 输出5V直流电
*
* @return
*/
@Override
public int output5V() {
int output = this.ac220.output220V();
return (output / 44);
}
}
/**
* 测试对象适配器
*/
public class TestObjectAdapter {
public static void main(String[] args) {
AC220 ac220 = new AC220();
PowerAdapter powerAdapter = new PowerAdapter(ac220);
System.out.println("输出电流:" + powerAdapter.output5V() + "V");
}
}
优点:根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承Adaptee的局限性问题,也不再要求Target必须是接口。使用成本更低,更灵活。
六、代理模式
什么是代理模式? 代理模式就是对其他对象提供一种代理以控制对这个对象的访问。
为什么需要使用代理? 代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理设计模式的种类:静态代理 ,动态代理
6.1 静态代理
静态代理就是写死了在代理对象中执行这个方法前后执行添加功能的形式,每次要在接口中添加一个新方法,则需要在目标对象中实现这个方法,并且在代理对象中实现相应的代理方法。
//抽取的公共功能: 目标和代理类
public interface Chuzu {
public void chuzu();
}
//房东类---被代理对象类
public class FangDong implements Chuzu {
public void chuzu() {
System.out.println("房东要出租房子");
}
}
//房产中介---代理类 【不但可以调用被代理对象的方法,还可以拥有自己的方法】
public class FCZJ implements Chuzu {
//被代理的对象
private FangDong target;
public FCZJ(FangDong target) {
this.target = target;
}
public void chuzu() {
intro();
target.chuzu();
watch();
}
public void watch(){
System.out.println("看房");
}
public void intro(){
System.out.println("房源介绍");
}
}
//测试
public class Test {
public static void main(String[] args) {
FangDong fangDong=new FangDong();
//创建房产中介对象
FCZJ fczj=new FCZJ(fangDong);
fczj.chuzu();
}
}
静态代理缺点: 需要客户端自己创建代理类对象。 如果接口中增加了一个方法后,需要代理类也增加该方法。
我们可以使用动态代理来解决上面的问题。
6.2 动态代理
动态代理的实现方式有两种:jdk动态代理 ,cglib动态代理
jdk动态代理:
//抽取的公共功能: 目标和代理类
public interface Chuzu {
public void chuzu();
}
//房东类---被代理对象类
public class FangDong implements Chuzu {
public void chuzu() {
System.out.println("房东要出租房子");
}
}
//代理工厂类--
package jdk动态代理;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
//被代理的对象
private Chuzu target;
public ProxyFactory(Chuzu target) {
this.target = target;
}
//获取代理对象。
public Chuzu getInstance(){
//java中提供了一个代理类---该类可以动态生成代理对象.
/*
ClassLoader loader, 被代理类使用的类加载器。
Class<?>[] interfaces, 被代理对象实现的接口。
InvocationHandler h: 当代理类调用代理方法时触发的函数。
*/
ClassLoader loader=target.getClass().getClassLoader();
Class<?>[] interfaces=target.getClass().getInterfaces();
InvocationHandler h=new InvocationHandler() {
//method:执行被代理对象的方法对象 args:执行的方法需要的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
intro();
//回调该方法--可以对原方法进行扩展 但是并没有修改原类的代码
Object result = method.invoke(target, args);
watch();
return null;
}
};
Object o = Proxy.newProxyInstance(loader,interfaces,h);
return (Chuzu) o;
}
public void watch(){
System.out.println("看房");
}
public void intro(){
System.out.println("房源介绍");
}
}
public class Test {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(new Fangdong());
//代理对象
Chuzu instance = proxyFactory.getInstance();
instance.mai();
}
}
JDK动态代理: 必须基于接口实现【被代理的类必须实现接口】。
cglib动态代理类:
public class Fangdong {
public void chuzu(){
System.out.println("房东出租房屋");
}
}
package cglib动态代理;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyFactory implements MethodInterceptor {
//被代理的对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//获取代理对象
public Object getInstance(){
//创建一个工具---生成代理对象
Enhancer enhancer=new Enhancer();
//指定被代理的的父类
enhancer.setSuperclass(target.getClass());
//代理的回调方法
enhancer.setCallback(this);
return enhancer.create();
}
//执行被代理的方法时,会执行该方法。
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
intro();
//调用被代理对象的方法
Object result = method.invoke(target, objects);
watch();
return result;
}
public void watch(){
System.out.println("看房");
}
public void intro(){
System.out.println("房源介绍");
}
}
package cglib动态代理;
public class Test {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(new Fangdong());
//获取代理对象
Fangdong instance = (Fangdong) proxyFactory.getInstance();
instance.chuzu();
}
}
七、观察者模式
7.1 概念
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
比如:微信公众号
7.2 结构
观察者模式的主要角色如下。
抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
7.3 实现
观察者模式首先有一个被观察类,这个类中有一个 ArrayList 存放观察者们。除此以外还应有类状态和设置和获取状态的方法,状态改变时通知所有观察者,观察者类可以有个抽象类,所有的观察者类继承这个抽象类,观察者类有它要观察的对象。
7.4 实例
需求:做一个案例,公众号推送消息,关注这个公众号的客户可以接收到消息。
1. 抽象主题---抽象公共号 1. 关注 2. 取消关注 3. 设置推送的消息 4. 群发 2. 具体主题--具体公众号类AAA 3. 抽象观察者---抽象用户 1. 接受消息 4. 具体观察者
package com.wjk.demo06;
public interface UserInterface {
/**
* 接受公众号发生的消息
* @param msg
*/
public void getMsg(String msg);
}
package com.wjk.demo06;
/**
* 观察者的角色
*/
public class User implements UserInterface{
private String name;
public User(String name) {
this.name = name;
}
@Override
public void getMsg(String msg) {
System.out.println(name+"接受公众号消息"+msg);
}
}
package com.wjk.demo06;
/**
* 抽象公众号类
*/
public interface PublicHaoInterface {
//关注公众号
public void add(UserInterface userInterface);
//取关公众号
public void remove(UserInterface userInterface);
//接受公众号的消息
public void setMsg(String msg);
//群发消息
public void qunfa();
}
package com.wjk.demo06;
import java.util.ArrayList;
import java.util.List;
public class AAAPublicHao implements PublicHaoInterface{
List<UserInterface> list=new ArrayList<>();
private String msg;
@Override
public void add(UserInterface userInterface) {
list.add(userInterface);
}
@Override
public void remove(UserInterface userInterface) {
list.remove(userInterface);
}
@Override
public void setMsg(String msg) {
this.msg=msg;
}
@Override
public void qunfa() {
for (UserInterface userInterface:list){
userInterface.getMsg(msg);
}
}
}
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/9 16:53
* @Version 1.0
*/
package com.wjk.demo06;
public class Test01 {
public static void main(String[] args) {
AAAPublicHao aaaPublicHao=new AAAPublicHao();
User user = new User("王振华");
User user1 = new User("刘壬衫");
User user2 = new User("赵雅浦");
User user3 = new User("王俊凯");
aaaPublicHao.add(user);
aaaPublicHao.add(user2);
aaaPublicHao.add(user3);
aaaPublicHao.add(user1);
aaaPublicHao.setMsg("今天星期三");
aaaPublicHao.qunfa();
System.out.println("**********************************");
aaaPublicHao.remove(user2);
aaaPublicHao.qunfa();
}
}
7.5 优缺点
观察者模式是一种对象行为型模式,其主要优点如下。
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。 目标与观察者之间建立了一套触发机制。
它的主要缺点如下。
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
八、模板方法模式
8.1 概念
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
8.2 意图
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定功能。
8.3 例子:炒菜
1. 买材料 2. 材料的处理 3. 起锅烧油 4. 放入材料 5. 放入调料 6. 翻炒均匀 7. 盛菜
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/10 9:20
* @Version 1.0
*/
package com.wjk.demo1110.demo01;
public abstract class ChaoCai {
/**
* 买材料
*/
public abstract void buy();
/**
* 处理材料
*/
public abstract void handle();
/**
* 起锅烧油
*/
public abstract void qiguoshaoyou();
/**
* 放入材料
*/
public abstract void putin();
/**
* 放入调料
*/
public abstract void tiaoliao();
/**
* 翻炒均匀
*/
public abstract void fanchao();
/**
* 盛菜出锅
*/
public abstract void chengCai();
public final void chaoCai(){
buy();
handle();
qiguoshaoyou();
putin();
tiaoliao();
fanchao();
chengCai();
}
}
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/10 9:58
* @Version 1.0
*/
package com.wjk.demo1110.demo01;
/**
* 模板方式模式
*/
public class Kelejichi extends ChaoCai{
@Override
public void buy() {
System.out.println("购买翅中");
}
@Override
public void handle() {
System.out.println("给鸡翅焯水");
}
@Override
public void qiguoshaoyou() {
System.out.println("起锅烧油");
}
@Override
public void putin() {
System.out.println("放入鸡翅炒");
}
@Override
public void tiaoliao() {
System.out.println("放入生抽,老抽,耗油和调制好的蜜料");
}
@Override
public void fanchao() {
System.out.println("倒入可乐,大火收汁");
}
@Override
public void chengCai() {
System.out.println("可乐鸡翅就出锅了");
}
}
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/10 10:01
* @Version 1.0
*/
package com.wjk.demo1110.demo01;
/**
* 模板方式
*/
public class Test01 {
public static void main(String[] args) {
Kelejichi kelejichi=new Kelejichi();
kelejichi.chaoCai();
}
}
九、策略模式
9.1 背景
在开发中经常遇到这种情况,实现某个功能有多种算法策略,我们可以根据不同环境或者条件选择不同的算法策略来完成该功能,比如查找、排序等,一种常用方式是硬编码在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过 if-else 或者 case 等条件判断语句来进行选择。但是如果需要增加新的算法策略,就需要修改封装算法类的源代码;更换查找算法,也需要修改客户端的调用代码。并且在这个类中封装了大量算法,也会使得该类代码较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。
如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?解决方法就是使用策略模式。
9.2 什么是策略模式
将类中经常改变或者可能改变的部分提取为作为一个抽象策略接口类,然后在类中包含这个对象的实例,这样类实例在运行时就可以随意调用实现了这个接口的类的行为。
比如定义一系列的算法,把每一个算法封装起来,并且使它们可相互替换,使得算法可独立于使用它的客户而变化,这就是策略模式
(1)环境类(Context):通过 ConcreteStrategy 具体策略类来配置,持有 Strategy 对象并维护对Strategy 对象的引用。可定义一个接口来让 Strategy 访问它的数据。
(2)抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy 定义的算法。
(3)具体策略类(ConcreteStrategy): Strategy 接口的具体算法。
演示例子:促销活动
package com.wjk.demo1110.demo02;
public interface Cuxiao {
//打折
public void dazhe();
}
package com.wjk.demo1110.demo02;
public class Shuang11 implements Cuxiao{
@Override
public void dazhe() {
System.out.println("双十一打11折");
}
}
package com.wjk.demo1110.demo02;
public class Shuang12 implements Cuxiao{
@Override
public void dazhe() {
System.out.println("双十二打12折");
}
}
package com.wjk.demo1110.demo02;
public class CuxiaoContext {
private Cuxiao cuxiao;
public Cuxiao getCuxiao() {
return cuxiao;
}
public void setCuxiao(Cuxiao cuxiao) {
this.cuxiao = cuxiao;
}
public void userCuxiao(){
cuxiao.dazhe();
}
}
/**
* Created by Intellij IDEA
*
* @author 王俊凯
* @Date: 2022/11/10 9:44
* @Version 1.0
*/
package com.wjk.demo1110.demo02;
public class Test01 {
public static void main(String[] args) {
CuxiaoContext cuxiaoContext=new CuxiaoContext();
cuxiaoContext.setCuxiao( new Shuang11());
cuxiaoContext.userCuxiao();
}
}
9.3 策略模式的优缺点
优点:
1、算法可以自由切换(策略类自由切换)。
2、避免使用多重条件判断。
3、扩展性良好(符合开闭原则)。
缺点:
1、策略类会增多。
2、所有策略类都需要对外暴露。
3、客户端必须知道所有的策略类,才能确定要调用的策略类。
策略模式使用场景:
1、 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现
总结:常见的设计模式
1. 单例模式。保证类只能创建一个实例。节省开销。 饿汉式 懒汉式【线程安全问题】volitale
[1]、防止JVM指令重排 [2]、保证线程之间的可见性。
1. 工厂模式: 简单工厂 和 工厂方法模式
2. 适配器模式:
3. 代理模式: [1]静态代理 [2]动态代理 (JDK原生动态代理和Cglib动态代理)
4. 模板方法模式
5. 策略模式
6. 观察者模式: 定义了一个一对多的依赖关系,当一的一方发生改变 多的一方会收到改