前言
设计模式,实质上是一套被反复使用的代码设计经验,它提供了在软件设计过程中重复性问题的解决方案。 其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。 设计模式的本质是建立在对类的封装性、继承性和多态性以及类的关联关系和组合关系等充分理解的基础上,对面向对象设计原则的实际运用。
软件设计七大原则
- 开闭原则:对扩展开放,对修改封闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。
- 单一职责原则:不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分。
- 里氏替换原则:任何基类可以出现的地方,子类一定可以出现。
- 依赖倒置原则:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
- 接口隔离原则:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的
- 迪米特法则:一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
- 合成复用原则:尽量首先使用合成/聚合的方式,而不是使用继承。
单例模式
概念:
单例(Singleton)模式的定义:指⼀个类只有⼀个实例,且该类能⾃⾏创建这个实例的⼀种模式。例如,Windows 中只能打开⼀个任务 管理器,这样可以避免因打开多个任务管理器窗⼝⽽造成内存资源的浪费,或出现各个窗⼝显⽰内容的不⼀致等错误。
java中单例模式是一种常见的设计模式,这里主要介绍两种:懒汉式单例、饿汉式单例。
特点 :
单例模式有 3 个特点:
1. 单例类只有⼀个实例对象;
2. 该单例对象必须由单例类⾃⾏创建;
3. 单例类对外提供⼀个访问该单例的全局访问点。
单例模式的优点和缺点
单例模式的优点:
单例模式可以保证内存⾥只有⼀个实例,减少了内存的开销。 可以避免对资源的多重占⽤。 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
单例模式⼀般没有接⼝,扩展困难。如果要扩展,则除了修改原来的代码,没有第⼆种途径,违背开闭原则。 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执⾏完,也不能模拟⽣成⼀个新的对象。 单例模式的功能代码通常写在⼀个类中,如果功能设计不合理,则很容易违背单⼀职责原则。
用法:
饿汉模式:
public class HungryMan { //如何保障我们只能创建一个 GirlFriend对象 //步骤[单例模式 -- 饿汉模式] //1.先将构造器私有化 //2.在类内部直接创建对象 //3.提供一个公用的静态方法返回 p1对象 private HungryMan(){} private static HungryMan HUNGRY_MANY; private static HungryMan getInstance(){ HungryMan hungryMan = new HungryMan(); return hungryMan; } }
懒汉模式:
public class LazyMan { //步骤[设计模式 -- 懒汉式] //1.构造器私有化 //2.定义一个static静态属性对象 //3.提供一个public的static方法,可以返回一个Cat对象 //4.懒汉式只有当用户使用getInstance时,才返回cat对象,后面再次调用时会返回上次创建的对象cat //从而保证了单例 private static LazyMan LazyMan; private LazyMan(){} public static LazyMan getInstance(){ if(LazyMan == null){ LazyMan lazyMan = new LazyMan(); } return LazyMan; } }
此外,在单线程中可以使用简单的懒汉模式创建对象,但在多线程创建对象中,
我们就不能保证创建的对象唯一性和创建对象的原子性。因此,我们需要在创建对象时增加一些条件,使得创建对象唯一。
//双重检测锁的懒汉式单例 DCL懒汉式 public class LazyMan02 { //volatile原子操作,保证在多线程中不会出现重复new private volatile static LazyMan02 LazyMan02; private LazyMan02(){} public static LazyMan02 getInstance(){ if(LazyMan02 == null){ LazyMan02 lazyMan02 = new LazyMan02(); } return LazyMan02; } }
此外,我们还有静态内部类的懒汉式
//静态内部类的懒汉式 public class Holder { private Holder(){} public static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER = new Holder(); } }
工厂模式
概念:
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
核心实质:
- 实例化对象不适用new,用工厂方法代替
- 将选择实现类,创建对象统一管理和控制,从而将调用者跟我们的实现类解耦
java中工厂模式是一种常见的设计模式,这里主要介绍三种:简单工厂模式、工厂方法、抽象工厂模式。
特点 :
简单工厂模式:专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。它又称为静态工厂方法模式。
工厂方法模式:工厂方法是粒度很小的设计模式,首先完全实现ocp,实现了可扩展。
抽象工厂模式:它有多个抽象产品类,每个抽象产品类可以派生出多个具体产品类,一个抽象工厂类,可以派生出多个具体工厂类,每个具体工厂类可以创建多个具体产品类的实例。
单例模式的优点和缺点
优点:
简单工厂模式:可以决定在什么时候创建哪一个产品类的实例,足够简单
工厂方法模式:是为了克服简单工厂模式的缺点(主要是为了满足OCP)而设计出来的。
抽象工厂模式:具体产品在应用层的代码隔离,无需关心创建的细节,将一个系列的产品统一到一起创建
缺点:
简单工厂模式:拓展困难,破坏了开闭原则.
工厂方法模式:假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。
抽象工厂模式:抽象工厂模式在于难于应付“新对象”的需求变动。难以支持新种类的产品。难以扩展抽象工厂以生产新种类的产品。
用法:
简单工厂模式:
1.创建实体类
public class Tesla implements Car{ @Override public void name() { System.out.println("特斯拉"); } }
public class WuLing implements Car{ @Override public void name() { System.out.println("五菱宏光"); } }
2.创建对象工厂
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; } } }
3.消费者直接调用工厂获取
public class Consumer { public static void main(String[] args) { //接口,所有的实现类,工厂 // Car car = new WuLing(); // Car car1 = new Tesla(); //工厂实现创建 Car car = CarFactory.getCar("五菱"); Car car1 = CarFactory.getCar("特斯拉"); car.name(); car1.name(); } }
工厂方法模式:
1.创建实体类
public class Tesla implements Car{ @Override public void name() { System.out.println("特斯拉"); } }
public class WuLing implements Car{ @Override public void name() { System.out.println("五菱宏光"); } }
2.创建总工厂Factory
public interface CarFactory { Car getCar(); }
3.编写各品牌车工厂interface总工厂
public class TeslaFactory implements CarFactory{ @Override public Car getCar() { return new Tesla(); } }public class WuLingFactory implements CarFactory{ @Override public Car getCar() { return new WuLing(); } }
4.顾客购车,直接在对应车工厂购车
public class Consumer { public static void main(String[] args) { Car car = new WuLingFactory().getCar(); Car car1 = new TeslaFactory().getCar(); car.name(); car1.name(); } }
抽象工厂模式:
1.创建可以生产工厂的工厂(SuperFactory)
public interface IProductFactory { //生产手机 IphoneProduct iphoneProduct(); //生成路由器 IRouterProduct iRouterProduct(); }
2.创建二级工厂
public class XiaomiFactory implements IProductFactory{ @Override public IphoneProduct iphoneProduct() { return new XIaomiPhones(); } @Override public IRouterProduct iRouterProduct() { return new XiaomiRouter(); } }
public class HuaweiFactory implements IProductFactory{ @Override public IphoneProduct iphoneProduct() { return new HuaweiPhones(); } @Override public IRouterProduct iRouterProduct() { return new HuaweiRouter(); } }
3.各品牌工厂实现功能接口,例:华为手机工厂
3.1 手机功能
public class HuaweiPhones implements IphoneProduct{ @Override public void start() { System.out.println("华为手机开机..."); } @Override public void shutdown() { System.out.println("华为手机关机..."); } @Override public void callup() { System.out.println("华为手机打电话..."); } @Override public void sendSMS() { System.out.println("华为手机发短信..."); } }
3.2路由器功能
public class HuaweiRouter implements IRouterProduct{ @Override public void start() { System.out.println("华为手机开启路由器..."); } @Override public void shutdown() { System.out.println("华为手机关闭路由器..."); } @Override public void openwife() { System.out.println("华为手机打开wifi..."); } @Override public void setting() { System.out.println("华为手机打开设置..."); } }
4.顾客购买
//客户端 public class Client{ public static void main(String[] args) { System.out.println("=============小米产品==============="); new XiaomiFactory().iphoneProduct().start(); new XiaomiFactory().iRouterProduct().openwife(); System.out.println("=============华为产品==============="); new HuaweiFactory().iphoneProduct().sendSMS(); new HuaweiFactory().iRouterProduct().setting(); } }
代理模式
概念:
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。例如顾客,房东,中介的关系
核心实质:
- 租客不用再去找房东租房,直接找中介即可
- 房东不用寻找租客,直接交给中介
- 中介还可以做除租房外的其他事情,例如:签合同,看房等
常见代理模式:静态代理模式,动态代理模式
特点:
代理模式的主要优点有:
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
代理对象可以扩展目标对象的功能;
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
其主要缺点是:
在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
增加了系统的复杂度;
用法:
静态代理模式
1.定义一个接口Rent
public interface Rent { public void rent(); }
2.创建房东Host
//房东 public class Host implements Rent{ @Override public void rent() { System.out.println("房东要出租房子!"); } }
3.创建中介Proxy
//静态代理模式 //代理者 public class Proxy implements Rent{ private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } @Override public void rent() { host.rent(); seeHouse(); sign(); fare(); } //看房子 public void seeHouse(){ System.out.println("中介带你看房"); } //签合同 public void sign(){ System.out.println("中介跟你签合同"); } //收中介费 public void fare(){ System.out.println("中介收取中介费"); } }
4.创建租客Client
public class Client { public static void main(String[] args) { //房东要出租房子 Host host = new Host(); //代理,中介带你租房,中介除了租房还会干一系列事情 Proxy proxy = new Proxy(host); //顾客不用直接面对房东,直接找中介租房 proxy.rent(); } }
动态代理模式:
1.定义一个被代理接口Rent
public interface Rent { public void rent(); }
2.创建房东Host
//房东 public class Host implements Rent{ @Override public void rent() { System.out.println("房东要出租房子!"); } }
3. 创建代理工厂类(核心)
package com.heima.proxy.demo03; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyInvocationHandle implements InvocationHandler { //被代理的接口 private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //处理代理实例,并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //动态代理的本质,就是使用反射机制实现 Object result = method.invoke(rent,args); return result; } //动态获取代理对象 public Object getProxy(){ /** * ClassLoader loader //指定当前目标对象使用类加载器 * Class<?>[] interfaces //目标对象实现的接口的类型,使用泛型方式确认类型 * InvocationHandler h //事件处理器 */ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); } }
4.创建租客Client
package com.heima.proxy.demo03; public class Client { public static void main(String[] args) { //创建原型对象 Host host = new Host(); //动态代理创建Proxy ProxyInvocationHandle pih = new ProxyInvocationHandle(); pih.setRent(host);//设置要代理的对象 //通过调用程序处理角色来处理我们要调用的接口对象 Rent proxy = (Rent) pih.getProxy();//这里的代理对象proxy是动态生成的,而不是我们自己new出来的 proxy.rent(); } }
原型模式
概念:
原型模式 : 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
实质:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 原型模式本质是一种克隆对象的方法,其核心是重写Object中的clone方法,调用该方法可以在内存中进行对象拷贝。
常见模式中有,深克隆,浅克隆
特点:
优点
减少代码:通过克隆现有对象来创建新对象,避免重新创建相同对象
提高性能:与实例化类相比,克隆操作更加高效。
灵活性:可以动态地添加或修改原型对象的属性和方法。
缺点
对象状态共享:由于多个实例共享同一个原型对象,对其中一个实例进行修改可能会影响其他实例。
对象构建复杂性:如果原型对象的构建过程比较复杂,可能会导致克隆操作变得复杂。
用法:
浅克隆:
1.创建原型
package com.heima.prototype.demo01; import java.util.Date; /* 1.实现一个接口 Cloneable 2.重写一个方法 clone() */ //原型 public class Video implements Cloneable{ private String name; private Date date; public Video() { } public Video(String name, Date date) { this.name = name; this.date = date; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } @Override public String toString() { return "Video{" + "name='" + name + '\'' + ", date=" + date + '}'; } }
2.从原型中copy
package com.heima.prototype.demo01; import javax.swing.*; import java.util.Date; //克隆之后的 public class Copy { public static void main(String[] args) throws CloneNotSupportedException { //原型对象v1 Date date = new Date(); Video v1 = new Video("狂神说设计模式",date); Video v2 = (Video) v1.clone(); System.out.println("v1-->" + v1); System.out.println("v2-->" + v2); System.out.println("======================="); date.setTime(2000303); System.out.println("v1-->" + v1); System.out.println("v2-->" + v2); // //克隆对象v2 // Video v2 = (Video) v1.clone(); // // System.out.println("v2-->" + v2); // System.out.println("v2-hash -->" + v2.hashCode()); } }
由结果可以看出,浅克隆时,v1和v2都来自于同一个内存,v1更新时v2也会相同更新
深克隆:
在浅克隆基础上,重写的clone方法中手动将属性也进行克隆
1.创建原型
package com.heima.prototype.demo02; import java.util.Date; public class Video implements Cloneable{ private String name; private Date date; public Video() { } public Video(String name, Date date) { this.name = name; this.date = date; } @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone(); //实现深克隆 Video v = (Video)obj; //将这个对象的属性也进行克隆 v.date = (Date) this.date.clone(); return obj; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } @Override public String toString() { return "Video{" + "name='" + name + '\'' + ", date=" + date + '}'; } }
2.从原型中copy
package com.heima.prototype.demo02; import java.util.Date; //克隆之后的 public class Copy { public static void main(String[] args) throws CloneNotSupportedException { //原型对象v1 Date date = new Date(); Video v1 = new Video("狂神说设计模式",date); Video v2 = (Video) v1.clone(); System.out.println("v1-->" + v1); System.out.println("v2-->" + v2); System.out.println("======================="); date.setTime(2000303); System.out.println("v1-->" + v1); System.out.println("v2-->" + v2); } }
适配器模式
概念:
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。如何简单易懂的让我们理解适配器模式呢,这里我们可以理解为:当我们通网上网时,网线无法与没有网孔插槽的电脑直接连接,也就是无法适配的问题,这时我们需要使用一个转接器也就是适配器来连接两端实现连通网络。
实质: 将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
特点:
优点:
1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用。
3、增加了类的透明度。
4、灵活性好。
缺点:
1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现。
2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
用法:
1.创建usb接口
package com.heima.adapter;
public interface NetToUsb {
//转接器,处理请求
public void handleRequest();
}
2.创建网线类Adaptee
package com.heima.adapter;
//要被适配的类: 网线
public class Adaptee {
public void request(){
//要上网的请求
System.out.println("连接网线上网...");
}
}
3.创建适配器Adapter,继承网线,实现usb接口
package com.heima.adapter;
//真正的适配器
public class Adapter extends Adaptee implements NetToUsb{
@Override
public void handleRequest() {
super.request();//可以上网了
}
}
4.创建Computer类连接网线上网
package com.heima.adapter;
//客户端类: 想上网,插不上网线
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);
}
}
但在这我们可以看到,我们没有使用到网线也能进行上网,这明显不符合我们的要求
1.这里我们重新创建适配器类Adapter2
package com.heima.adapter;
//1.继承
//2.组合
//真正的适配器,需要连接USB,连接网线~
public class Adapter2 implements NetToUsb {
//组合模式,定义Adaptee属性,动态将网线连入适配器
private Adaptee adaptee;
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void handleRequest() {
//上网的具体实现,找一个转接头
adaptee.request();//可以上网
}
}
2.在Computer类中连接适配器Adapter2
package com.heima.adapter;
/**
* @Author 黑麻哥
* @Date 2023/10/27 18:21
* @Version 1.0
*/
//客户端类: 想上网,插不上网线
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);
// System.out.println("============");
Computer computer = new Computer();//电脑
Adaptee adaptee = new Adaptee();//网线
Adapter2 adapter = new Adapter2(adaptee); //转接器
computer.net(adapter);
}
}
3.成功连接网线上网....