设计模式学习(Java实现)
1. 什么是设计模式
设计模式是对于代码开发经验的总结,用于提高代码的可复用性、可维护性、可读性、稳健性以及安全性 由GoF(四人帮)合作设计,一共收录了23种设计模式
设计模式的意义:
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解
使用设计模式的优点:
- 可以提高程序员的思维能力、编程能力和设计能力
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期
- 使设计的代码可重用性高,可读性强,可靠性高,灵活性好,可维护性强
设计模式的基本要素:
- 模式名称
- 需要解决的问题
- 解决方案
- 效果
23种设计模式:
- 创建型模式: (描述如何去创建对象,目的是为了将对象的创建和使用分离) 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
- 结构型模式: (描述如何将类/对象按照某种布局组合成更大的结构) 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
- 行为型模式: (描述类/对象之间的协作,以完成单个类无法完成任务,主要是分配职责) 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
2. OOP的七大原则
- 开闭原则:对扩展开放,对修改关闭 (也就是说扩展的代码尽量不影响原来的代码,尽量做到独立)
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立 (也就是说,子类应该是在父类基础上的拓展,添加新的方法,而尽量不要去修改父类原有的方法等)
- 依赖倒置原则:要面向接口编程,不要面向实现编程
- 单一职责原则:控制类的粒度大小,将对象解耦,提高其内聚性 (也就是说,一个对象不应该承担很多职责,否则会有很多冗余,粒度太粗)
- 接口隔离原则:要为各个类建立它们需要的专用接口
- 迪米特法则:只和直接相关联的对象通信,而不跟其他对象通信
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
3.单例模式
A.饿汉式单例模式
//饿汉式单例模式
//可能会浪费内存
//Hungry.java
public class Hungry{
//先占用空间,不一定使用,可能浪费
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
B.懒汉式单例模式
//懒汉式单例模式
//有线程安全问题
//LazyMan.java
public class LazyMan{
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
懒汉式单例模式是线程不安全的:
//懒汉式单例模式
//有线程安全问题
//LazyMan.java
public class LazyMan{
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
//多线程并发
public static void main(String[] args){
for(int i = 0;i < 10;i++){
new Thread(() ->{
LazyMan.getInstance();
}).start();
}
}
}
那么,为了解决懒汉式单例模式的线程不安全问题,我们就应该给他加锁。
加锁也有两种模式:
C.懒汉式单例模式-单锁
//LazyMan.java
public class LazyMan{
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
synchronized(LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args){
for(int i = 0;i < 10;i++){
new Thread(() ->{
LazyMan.getInstance();
}).start();
}
}
}
D.懒汉式单例模式-双重检测锁(DCL)
//双重检测锁模式的懒汉式单例模式(即DCL懒汉式)
//通过加锁来解决懒汉式单例模式的线程不安全问题
//LazyMan.java
public class LazyMan{
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized(LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args){
for(int i = 0;i < 10;i++){
new Thread(() ->{
LazyMan.getInstance();
}).start();
}
}
}
关于volatile:
其实,严格来说,就算是DCL懒汉式单例模式也是线程不安全的,为什么呢? 因为lazyMan = new LazyMan()这个操作并不是原子性操作,它需要走三步:
- 分配内存空间
- 执行构造方法,初始化对象
- 把这个对象指向所分配的空间
所以有可能发生指令重排! 可能执行顺序是:123 也可能是:312 所以需要volatile~
E.静态内部类单例模式
public class Holder{
private Holder(){
}
public static Holder getInstance(){
return InnnerClass.HOLDER;
}
public static class InnnerClass{
private static final Holder HOLDER = new Holder();
}
}
以上单例模式都不安全,都能被反射破坏,所以要看看枚举类单例模式!它不会被反射破坏!
F.枚举类单例模式
//EnumSingle.java
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSingle{
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException,IllegalAccessException,InvocationTargetException,InstantiationException{
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
4.工厂模式
工厂模式的作用:
实现了创建者和调用者的分离
工厂模式的详细分类:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
核心本质:
- 实例化对象不使用new,用工厂方法代替
- 将选择实现类、创建对象统一管理和控制,从而将调用者跟我们的实现类解耦
三种模式:
- 简单工厂模式:(静态工厂模式) 用来生产同一等级结构中的任意产品
- 工厂方法模式: 用来生产同一等级结构中的固定产品
- 抽象工厂模式: 围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂 (下一节拓展开来讲)
看代码其实会清晰很多:
//简单工厂模式 == 静态工厂模式
//若果需要增加一个新的产品,在不修改代码的情况下是做不到的
//不满足开闭原则
public class CarFactory{
public static Car getCar(String car){
if(car.equals("五菱")){
return new WuLing();
}
else if(car.equals("特斯拉")){
return new Tesla();
}
else {
return null;
}
}
}
public Customer{
public static void main(String[] args){
//使用new创建
//Car car = new WuLing();
//Car car2 = new Tesla();
//使用工厂创建
Car car = CarFactory.getCar("五菱");
Car car = CarFactory.getCar("特斯拉");
car.name();
car2.name();
}
}
//工厂方法模式
//满足了开闭原则,但是增加了代码量
public class Consumer{
public static void main(String[] args){
Car car = new TeslaFactory().getCar();
Car car2 = new WulingFactory().getCar();
Car car3 = new MobaiFactory().getCar();
}
}
public interface CarFactory{
Car getCar();
}
public class TeslaFactory implements CarFactory{
@Override
public Car getCar(){
return new Tesla();
}
}
public class WulingFactory implements CarFactory{
@Override
public Car getCar(){
return new WuLing();
}
}
public class MobaiFactory implements CarFactory{
@Override
public Car getCar(){
return new Mobai();
}
}
简单工厂模式vs工厂方法模式:
结构复杂度:简单工厂模式较好 代码复杂度:简单工厂模式较好 编程复杂度:简单工厂模式较好 管理复杂度:简单工厂模式较好
根据设计原则来看:工厂方法模式更好 根据实际业务来看:简单工厂模式更好
5.抽象工厂模式
定义:
抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类 (也就是把工厂再进行抽象,建立工厂的工厂)
适用场景:
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现
什么是产品族&产品等级结构呢?
产品族:是指同一个工厂生产的,位于不同产品等级结构中的一组产品(比如iphone、ipad、macbook是一个产品族)
产品等级结构:即产品的继承结构
图示:
代码演示:
//抽象工厂模式
//手机产品接口
//IphoneProduct.java
public interface IphoneProduct{
void start();
void shutdown();
void call();
void sendMessage();
}
//路由器产品接口
//IrouterProduct.java
public interface IrouterProduct{
void start();
void shutdown();
void openWiFi();
void setting();
}
//小米手机
//XiaomiPhone.java
public class XiaomiPhone implements IphoneProduct{
@Override
public void start(){
System.out.println("开启小米手机");
}
@Override
public void shutdown(){
System.out.println("关闭小米手机");
}
@Override
public void call(){
System.out.println("小米打电话");
}
@Override
public void sendMessage(){
System.out.println("小米发信息");
}
}
//华为手机
//HuaweiPhone.java
public class HuaweiPhone implements IphoneProduct{
@Override
public void start(){
System.out.println("开启华为手机");
}
@Override
public void shutdown(){
System.out.println("关闭华为手机");
}
@Override
public void call(){
System.out.println("华为打电话");
}
@Override
public void sendMessage(){
System.out.println("华为发信息");
}
}
//小米路由器
//XiaomiRouter.java
public class XiaomiRouter implements IrouterProduct{
@Override
public void start(){
System.out.println("启动小米路由器");
}
@Override
public void shutdown(){
System.out.println("关闭小米路由器");
}
@Override
public void openWiFi(){
System.out.println("打开小米Wifi");
}
@Override
public void setting(){
System.out.println("设置小米路由器");
}
}
//华为路由器
//HuaweiRouter.java
public class HuaweiRouter implements IrouterProduct{
@Override
public void start(){
System.out.println("启动华为路由器");
}
@Override
public void shutdown(){
System.out.println("关闭华为路由器");
}
@Override
public void openWiFi(){
System.out.println("打开华为Wifi");
}
@Override
public void setting(){
System.out.println("设置华为路由器");
}
}
//生产抽象产品工厂
//IProductFactory.java
public interface IProductFactory{
//生产手机
IphoneProduct iphoneProduct();
//生产路由器
IrouterProduct irouterProduct();
}
//XiaomiFactory.java
public class XiaomiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct(){
return new XiaomiPhone();
}
@Override
public IrouterProduct irouterProduct(){
return new XiaomiRouter();
}
}
//HuaweiFactory.java
public class HuaweiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct(){
return new HuaweiPhone();
}
@Override
public IrouterProduct irouterProduct(){
return new HuaweiRouter();
}
}
//Client.java
public class Client{
public static void main(String[] args)
System.out.println("=========小米系列产品=========");
//小米工厂
XiaomiFactory xiaomiFactory = new XiaomiFactory();
IphoneProduct iphoneProduct = xiaomiFactory.iphoneProduct();
iphoneProduct.call();
iphoneProduct.sendMessage();
IrouterProduct irouterProduct = xiaomiFactory.irouterProduct();
irouterProduct.openWiFi();
irouterProduct.setting();
System.out.println("=========华为系列产品=========");
//小米工厂
HuaweiFactory huaweiFactory = new huaweiFactory();
IphoneProduct iphoneProduct = huaweiFactory.iphoneProduct();
iphoneProduct.call();
iphoneProduct.sendMessage();
IrouterProduct irouterProduct = huaweiFactory.irouterProduct();
irouterProduct.openWiFi();
irouterProduct.setting();
}
抽象工厂模式的优点;
- 具体产品在应用层的代码隔离,无需关心创建的细节
- 将一个系列的产品统一到一起创建
抽象工厂模式的缺点:
- 规定了所有可能被创建的产品集合,产品族中想要拓展新的产品比较困难(所以会比较适合比较稳定的系统)
- 增加了系统的抽象性和理解难度
6.建造者模式
建造者模式提供了一种创建对象的最佳方式
用户只需要给出指定复杂对象的类型和内容,建造者模式复杂按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
定义:
将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示
主要作用:
在用户不知道对象的建造过程和细节的情况下就可能直接创建复杂的对象
举个具体例子:
想要造一栋房子,就需要建筑公司(指挥者)指挥工人(具体建造者)去造房子(产品)
角色分析:
通过代码来康康:
//抽象的建造者
//Builder.java
public abstract class Builder{
abstract void buildA(); //打地基
abstract void buildB(); //钢筋
abstract void buildC(); //铺电线
abstract void buildD(); //粉刷
abstract Product getProduct();
}
//产品:房子
public class Product{
private String buildA;
private String buildB;
private String buildC;
private String buildD;
public String getBuildA(){
return buildA;
}
public void setBuildA(String buildA){
this.buildA = buildA;
}
public String getBuildB(){
return buildB;
}
public void setBuildB(String buildB){
this.buildB = buildB;
}
public String getBuildC(){
return buildC;
}
public void setBuildC(String buildC){
this.buildC = buildC;
}
public String getBuildD(){
return buildD;
}
public void setBuildD(String buildD){
this.buildD = buildD;
}
@Override
public String toString(){
return "Product{" +
"buildA = '" + buildA + '\'' +
",buildB = '" + buildB + '\'' +
",buildC = '" + buildC + '\'' +
",buildD = '" + buildD + '\'' +
'}';
}
}
//Worker.java
public class Worker extends Builder{
private Product product;
public Worker(){
product = new Product();
}
@Override
void buildA(){
product.setBuildA("打地基");
System.out.println("打地基");
}
@Override
void buildB(){
product.setBuildB("钢筋");
System.out.println("钢筋");
}
@Override
void buildC(){
product.setBuildC("铺电线");
System.out.println("铺电线");
}
@Override
void buildD(){
product.setBuildD("粉刷");
System.out.println("粉刷");
}
@Override
Product getProduct(){
return product;
}
}
//指挥:负责指挥构建一个工程,描述和决定工程如何构建
//Director.java
public class Director{
//指挥工人按照顺序建房子
public Product build(Builer builder){
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();
}
}
//Test.java
public class Test{
public static void main(String[] args){
Director director = new Director();
Product build = director.build(new Worker());
System.out.println(build.toString());
}
}
对建造者模式的一些补充说明:
- Director类在Builder模式中具有很重要的作用,用于指导具体建造者如何建造产品,控制调用先后顺序,并向调用者返回完整的产品类。 有些情况下需要简化系统结构,可以把Director和抽象建造者结合
- 通过静态内部类方式实现零件无序装配构造,这种方式使用起来更加灵活,更符合定义。 内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,且无需改变具体的构造方式,就能生产出不同的复杂产品
另一种方式:
举个例子:
//Builder.java
public abstract class Builder{
abstract Builder buildA(String msg); //汉堡
abstract Builder buildB(String msg); //可乐
abstract Builder buildC(String msg); //薯条
abstract Builder buildD(String msg); //甜点
abstract Product getProduct();
}
//Worker.java
public class Worker extends Builder{
private Product product;
public Worker(){
product = new Product();
}
@Override
Builder buildA(String msg){
product.setBuildA(msg);
return this;
}
@Override
Builder buildB(String msg){
product.setBuildB(msg);
return this;
}
@Override
Builder buildC(String msg){
product.setBuildC(msg);
return this;
}
@Override
Builder buildD(String msg){
product.setBuildD(msg);
return this;
}
@Override
Product getProduct(){
return product;
}
}
//产品:套餐
public class Product{
private String BuildA = "汉堡";
private String BuildB = "可乐";
private String BuildC = "薯条";
private String BuildD = "甜点";
public String getBuildA(){
return BuildA;
}
public void setBuildA(String buildA){
BuildA = buildA;
}
public String getBuildB(){
return BuildB;
}
public void setBuildB(String buildB){
BuildB = buildB;
}
public String getBuildC(){
return BuildC;
}
public void setBuildC(String buildC){
BuildC = buildC;
}
public String getBuildD(){
return BuildD;
}
public void setBuildD(String buildD){
BuildD = buildD;
}
@Override
public String toString(){
return "Product{" +
"BuildA = '" + BuildA + '\'' +
",BuildB = '" + BuildB + '\'' +
",BuildC = '" + BuildC + '\'' +
",BuildD = '" + BuildD + '\'' +
'}';
}
}
//Test.java
public class Test{
public static void main(String[] args){
//服务员
Worker worker = new Worker();
//默认搭配套餐
Product product1 = worker.getProduct();
//自由搭配套餐:链式编程
Product product2 = worker.buildA(msg:"全家桶").buildB("雪碧").getProduct();
System.out.println(product1.toString());
System.out.println(product2.toString());
}
}
建造者模式的优点
- 产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节
- 将复杂的创建步骤分解在不同的方法中,使得创建过程更加清晰
- 具体的建造者类之间是相互独立的,这有利于紫铜的扩展。增加新的具体建造者无需修改原有类库的代码,符合“开闭原则”
建造者模式的缺点
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围收到一定的限制
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者来实现这种变化,导致系统变得很庞大
建造者模式vs抽象工厂模式
- 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族
- 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象
- 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车
7.原型模式
先讲讲深拷贝与浅拷贝的区别:
图示
- 浅拷贝:
- 深拷贝:
直接上代码:
- 浅拷贝:
//原型模式
/*
1. 实现一个接口:cloneable
2. 重写一个方法:clone()
*/
import java.util.Date;
public class Video implements Cloneable{
private String name;
private Date createTime;
@Override
protected Object clone() throws ClassNotFoundException{
return super.clone();
}
Video(){
}
public Video(String name,Date createTime){
this.name = name;
this.createTime = createTime;
}
public String getName(){
return name;
}
public Date getCreateTime(){
return createTime;
}
public void setName(String name){
this.name = name;
}
public void setCreateTime(Date createTime){
this.createTime = createTime;
}
public String toString(){
return "Video{" + name + '\'' + ", createTime = " + createTime + '}';
}
}
//浅拷贝
//客户端:克隆
public class Bilibili{
public static void main(String[] args){
//原型对象
Date date = new Date();
Video v1 = new Video(name:"涛涛说Java",date);
Video v2 = (Video) v1.clone();
System.out.println("v1 => " + v1);
System.out.println("v1 => hash: " + v1.hashCode());
System.out.println("v2 => " + v2);
System.out.println("v2 => hash:" + v2.hashCode());
System.out.println("======================");
date.setCreateTime(123456);
System.out.println("v1 => " + v1);
System.out.println("v1 => hash: " + v1.hashCode());
System.out.println("v2 => " + v2);
System.out.println("v2 => hash:" + v2.hashCode());
}
}
- 深拷贝:
import java.util.Date;
public class Video implements Cloneable{
private String name;
private Date createTime;
//对clone()方法的重写 实现深克隆
@Override
protected Object clone() throws ClassNotFoundException{
Object obj = super.clone();
Video v = (Video) obj;
v.createTime = (Date) this.createTime.clone();
}
Video(){
}
public Video(String name,Date createTime){
this.name = name;
this.createTime = createTime;
}
public String getName(){
return name;
}
public Date getCreateTime(){
return createTime;
}
public void setName(String name){
this.name = name;
}
public void setCreateTime(Date createTime){
this.createTime = createTime;
}
public String toString(){
return "Video{" + name + '\'' + ", createTime = " + createTime + '}';
}
}
//深拷贝(改造clone()方法,也就是将这个对象的属性也一起克隆)
public class Bilibili{
public static void main(String[] args){
//原型对象
Date date = new Date();
Video v1 = new Video(name:"涛涛说Java",date);
Video v2 = (Video) v1.clone();
System.out.println("v1 => " + v1);
System.out.println("v1 => hash: " + v1.hashCode());
System.out.println("v2 => " + v2);
System.out.println("v2 => hash:" + v2.hashCode());
System.out.println("======================");
date.setCreateTime(123456);
System.out.println("v1 => " + v1);
System.out.println("v1 => hash: " + v1.hashCode());
System.out.println("v2 => " + v2);
System.out.println("v2 => hash:" + v2.hashCode());
}
}
8.适配器模式
适配器模式的作用
将一个类的接口转换成客户希望的另一个接口。 Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作
角色分析
- 目标接口:客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口
- 需要适配的类:需要适配的类或适配器类
- 适配器:通过包装一个需要适配的对象,把原接口转换成目标对象
图示:
代码演示
//要被适配的类:网线
//Adaptee.java
public class Adaptee{
public void request(){
System.out.println("连接网线上网");
}
}
//Computer.java
public class Computer{
//电脑需要连接上转接器才能上网
public void net(NetToUsb adapter){
//上网的具体实现,但是得先找一个转接头
adapter.handleRequest();
}
public static void main(String[] args){
Computer computer = new Computer(); //电脑
Adaptee adaptee = new Adaptee(); //网线
Adapter adapter = new Adapter(); //转接器
computer.net(adapter);
}
}
//接口转换器的抽象实现
//NetToUsb.java
public interface NetToUsb{
//处理请求,网线插到Usb上
public void handleRequest();
}
//真正的适配器,需要连接USB还有网线
//Adapter.java
//1.继承(类适配器,单继承)
public class Adapter extends Adaptee implements NetToUsb{
@Override
public void handleRequest(){
super.request();
//可以上网了
}
}
另一种实现方式(better)
//Adapter2.java
//2.组合(对象适配器,常用)
public class Adapter2 implements NetToUsb{
private Adaptee adaptee;
public Adapter2(Adaptee adaptee){
this.adaptee = adaptee;
}
@Override
public void handleRequest(){
adaptee.request();
//可以上网了
}
}
//客户端类:想上网,但是插不上网线(所以才需要一个适配器,也就是转接头)
//Computer.java
public class Computer{
//电脑需要连接上转接器才能上网
public void net(NetToUsb adapter){
//上网的具体实现,但是得先找一个转接头
adapter.handleRequest();
}
public static void main(String[] args){
Computer computer = new Computer(); //电脑
Adaptee adaptee = new Adaptee(); //网线
Adapter2 adapter = new Adapter2(adaptee); //转接器
computer.net(adapter);
}
}
对象适配器的优点:
- 一个对象适配器可以把多个不同的适配器适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可以通过该适配器进行适配
类适配器的缺点:
- 对于Java,C#等不支持多重继承的语言,一次最多只能适配yige适配器类,不能同时适配多个适配者
- 在Java,C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用具有一定的局限性
9.桥接模式
定义:
是将抽象部分与它的实现部分分离,使它们都可以独立地变化 又称为柄体(Handle and Body)模式或接口(interface)模式
多层继承结构vs桥接模式:
图示:
- 多层继承结构:
- 桥接模式:
上代码演示一下:
//桥接模式
public interface Brand{
void info();
}
//联想品牌
public class Lenovo implements Brand{
@Override
public void info(){
System.out.print("联想");
}
}
//苹果品牌
public class Apple implements Brand{
@Override
public void info(){
System.out.print("苹果");
}
}
//抽象的电脑类型
public abstract Computer{
//组合
protected Brand brand;
public Computer (Brand brand){
this.brand = brand;
}
public void info(){
//自带品牌
brand.info();
}
}
class Desktop extends Computer{
public Desktop(Brand brand){
super(brand);
}
@Override
public void info(){
super.info();
System.out.println("台式机");
}
}
class Laptop extends Computer{
public Laptop(Brand brand){
super(brand);
}
@Override
public void info(){
super.info();
System.out.println("笔记本");
}
}
public class Test{
public static void main(String[] args){
//苹果笔记本
Computer computer = new Laptop(new Apple());
computer.info();
//联想台式机
Computer computer2 = new Desktop(new Lenovo());
computer2.info();
}
}
桥接模式的好处:
- 桥接模式是比多继承方案更好的解决方法,极大地减少了子类的个数,从而降低管理和维护的成本。不像多继承方案,违背了类的单一职责原则,复用性较差,类的个数也很多;
- 桥接模式提高了系统的扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则
桥接模式的劣势:
- 桥接模式的引入会增加系统的理解与设计模式,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程
- 桥接模式要求正确识别系统中两个独立变化的维度,因此其使用范围具有一定的局限性
10.静态代理模式
角色分析:
- 抽象角色:
- 真实角色:
- 代理角色:
- 客户:访问代理对象的人
上代码分析-1:
- 接口
public interface Rent{
public void rent();
}
- 真实角色(更加纯粹!)
//房东
public class Host implements Rent{
public void rent(){
System.out.println("房东出租房子。");
}
}
- 代理角色
//租客
public class Client{
public static void main(String[] args){
//房东要出租房子
Host host = new Host();
//直接跟房东租房
//host.rent();
//代理
Proxy proxy = new Proxy(host);
proxy.rent();//附带中介自己的附加操作
}
}
- 客户端访问代理角色
//中介(代理)
public class Proxy implements Rent{
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host = host;
}
public void rent(){
seeHouse();
sign();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("中介带你看房");
}
//签合同
public void sign(){
System.out.println("签租赁合同");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
代理模式的好处:
- 可以使真实角色的操作更加纯粹(只需要专注于自己的业务,而不用在乎公共业务)
- 公共业务就交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
代理模式的缺点:
- 一个真实角色就会产生一个代理角色:代码量会翻倍,所以开发效率会变低
上代码分析-2:
- 接口
//用户需求
public interface UserService{
public void add();
public void delete();
public void update();
public void query();
}
- 真实角色(更加纯粹!)
//真实对象
public class UserServiceImpl implements UserService{
public void add(){
//日志
//System.out.println("使用了add()方法");
System.out.println("增加了一个用户");
}
public void delete(){
System.out.println("删除了一个用户");
}
public void update(){
System.out.println("修改了一个用户");
}
public void query(){
System.out.println("查询了一个用户");
}
}
- 代理角色
//代理(日志记录)
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService){
this.userService = userService;
}
public void add(){
log(msg:"add");
userService.add();
}
public void delete(){
log(msg:"delete");
userService.delete();
}
public void update(){
log(msg:"update");
userService.update();
}
public void query(){
log(msg:"query");
userService.query();
}
public void log(String msg){
System.out.println("[Debug] 使用了" + msg + "方法");
}
}
- 客户端访问代理角色
//客户端
public class Client{
public static void main(String[] args){
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
proxy.update();
}
}
11.动态代理模式
角色分析
- 抽象角色:
- 真实角色:
- 代理角色:
- 客户:访问代理对象的人
动态代理的代理类是动态生成的,不是我们直接写好的
动态代理分为两大类:
- 基于接口的动态代理√ JDK的动态代理
- 基于类的动态代理 cglib
- java字节码实现 javasist
需要了解两个类:(自己去康康文档8!)
- Proxy类
- InvocationHandler类
代码演示
- 接口
public interface Rent{
public void rent();
}
- 真实角色(更加纯粹!)
//房东
public class Host implements Rent{
public void rent(){
System.out.println("房东出租房子。");
}
}
- 代理角色
//这个类用来自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler{
//被代理的接口
private void setRent(Rent rent){
this.rent = rent;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),h:this);
}
//处理代理实例,并返回结果
public Object invoke(){
//动态代理的本质,就是使用反射机制实现
Object result = method.invoke(rent,args);
return result;
}
}
- 客户端访问代理角色
public class Client{
public static void main(String[] args){
//真实角色
Host host = new Host();
//代理角色:暂时没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象
pih.setRent(host);
Rent proxy = (Rent)pih.getProxy();
proxy.rent();
}
}
动态代理的好处:
- 可以使真实角色的操作更加纯粹(只需要专注于自己的业务,而不用在乎公共业务)
- 公共业务就交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
- 一个动态代理类代理的是一个接口,一般就是对应一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可