设计模式的理解
一. 概述
-
设计模式的本质是面向对象设计原则的实际的应用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分的理解。
-
正确使用设计模式具有以下的优点:
- 可以提高程序员的思维能力、编程能力和设计能力
- 使得程序设计更加标准更加工程化,使软件开发的效率大大提高,从而可以缩短软件的开发周期
- 使设计的代码的重用性高,可读性强,可靠性强,灵活性好,可维护性强
-
创建型模式
- 单例模式,工厂模式,抽象工厂模式,建造者模式,原型模式
-
结构型模式
- 适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
-
行为型模式
- 模板方法方式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式,访问者模式
二. OOP七大原则
- 开闭原则:对外扩展,对修改关闭
- 里氏替换原则:继承必须确保超类所拥有的的性质在子类中仍然成立
- 依赖倒置原则:面向编程接口,不要面向实现编程
- 单一职责原则:控制类的粒度的大小,将对象解耦,提高其内聚性
- 接口隔离原则:要为各个类建立他们需要的专用的接口
- 迪米特法则:只与你的直接朋友交谈,不和陌生人说话
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承的关系来实现
三. 设计模式分析
1. 单例模式
饿汉式(不管直接先创建)
// 单例模式中的饿汉式
public class Hungry {
// 在单例模式中我们是需要构造器私有化的,但是这种方式会造成空间的浪费
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式(用的时候在创建)
// 懒汉式
public class Lazy{
// 我们首先要将构造器私有化
private Lazy(){
}
private static Lazy lazy;
// 我们在需要使用的时候在进行创建,这个在单线程的情况下是安全的,
// 但是在多线程情况下是不安全的
public static Lazy getInstance(){
if (lazy == null)
rerutn new Lazy();
return lazy;
}
}
DCL 懒汉式,其实是通过增加判断,以及锁的方式,但是由于new一个对象这个操作不是原子性的操作,所以我们需要增加volatile关键字,来防止指令的重排序。
// DCL方式
// 但是这个其实可以通过我们的反射来破坏掉的,要想真正的做到安全的话,需要我们使用枚举类来实现[在java中不能尝试使用枚举类来破解!]
public class Lazy{
// 我们首先要将构造器私有化
private Lazy(){
}
private volatile static Lazy lazy;
public static Lazy getInstance(){
if (lazy == null){
synchronized(Lazy.class){
if (lazy == null){
lazy = new Lazy(); // 这个不是原子性操作
/*
1. 分配内存空间
2. 执行构造方法,初始化对象
3. 把对象指向这个空间
*/
}
}
}
return lazy;
}
}
DCL 这种方式虽然看似是比较安全的,但是还是可以通过反射的方式进行破坏的!此时,我们可以通过增加变量的方式,防止被破坏,但是其实也还是可以通过反射被破解的。
public class LazyMan {
private static boolean feifei = false;
// 构造器私有化
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok!");
synchronized (LazyMan.class){
if (feifei == false){
feifei = true;
}else{
throw new RuntimeException("不要试图使用反射的方式破坏单例模式!");
}
if (lazyMan != null){
throw new RuntimeException("不要试图使用反射的方式破坏单例模式!");
}
}
}
// 此时不会发生指令重排的情况
private volatile static LazyMan lazyMan;
// 双重检测DCL
public static LazyMan getInstance(){
if (lazyMan == null){
synchronized (LazyMan.class){
if (lazyMan == null){
// 不是原子性的操作
// 1. 分配内存空间
// 2. 执行构造方法,初始化对象
// 3. 把这个对象指向内存空间
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
使用静态内部类也是可以单例模式的,同样的可以被反射破坏
// 静态内部内实现
public class Holder {
// 构造器私有化!
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
使用枚举类,是可以防止通过反射破坏的,枚举类中是没有无参构造器的
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
2. 工厂模式
- 作用:
- 实现创建者和调用者的分离
- 可以分成:简单工厂模式,工厂方法模式以及抽象工厂模式
简单工厂模式[静态工厂模式]
- 增加一个新的产品的时候,是需要修改代码的[不满足开闭原则]
// 我们是通过直接调用工厂来创建自己需要的车
public class Consumer {
public static void main(String[] args) {
// 1. 传统的方式需要了解接口,所有的实现类
// Car car = new Wuling();
// Car car1 = new Tesla();
// 2. 使用工厂的方式
Car car = CarFactory.getCar("五菱");
Car car1 = CarFactory.getCar("特斯拉");
car.name();
car1.name();
}
}
工厂方法模式
- 在增加新的产品的时候,可以直接增加即可,不需要修改原来的代码
- 在不修改已有类的前提下,通过增加新的工厂类实现扩展
// 每种类型的车我们都有自己对应的工厂,我们从各自的工厂拿车
public class Consumer {
public static void main(String[] args) {
// 此时我们是可以横向扩展的
Car car = new WulingFactory().getCar();
Car car1 = new TeslaFactory().getCar();
Car car2 = new MobaiFactory().getCar();
car.name();
car1.name();
car2.name();
}
}
根据设计原则是工厂方法模式比较好,但是在实际的业务中,我们使用的是简单工厂模式!
3. 抽象工厂模式
- 定义:抽象工厂模式提供了一个创建一系列相关的或者相互依赖对象的接口,无需指定他们具体的类
- 适用场景:
- 客户端[应用层]不依赖于产品类实例如何被创建、实现等细节
- 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现
- 优点:
- 具体产品在应用层的代码隔离,无需关心创建的细节
- 将一个系列的产品统一到一起进行创建
- 缺点:
- 规定了所有可能被创建的产品的集合,产品簇中扩展新的产品困难
- 增加了系统抽象性换个理解难度
抽象工厂模式的结构图
// 每个厂商有自己的工厂,厂商的工厂是有一个公共的工厂接口去实现
public class Client {
public static void main(String[] args) {
System.out.println("=========小米系列产品=========");
// 创建小米的工厂
XiaomiFactory xiaomiFactory = new XiaomiFactory();
IphoneProduct iphoneProduct = xiaomiFactory.iphoneProduct();
iphoneProduct.start();
RouterProduct routerProduct = xiaomiFactory.routerProduct();
routerProduct.openwifi();
System.out.println("========华为系列产品=======");
// 创建华为的工厂
HuaweiFactory huaweiFactory = new HuaweiFactory();
IphoneProduct iphoneProduct1 = huaweiFactory.iphoneProduct();
iphoneProduct1.sendSMS();
RouterProduct routerProduct1 = huaweiFactory.routerProduct();
routerProduct1.openwifi();
}
}
小结
- 简单工厂模式[静态工厂模式]
- 虽然在某种程度上是不符合设计原则的,但是在实际使用中最多!
- 工厂方法模式
- 在不修改已有类的前提下,通过增加新的工厂类实现扩展
- 抽象工厂模式
- 不可以增加产品,但是可以增加产品族
需要在稳定的产品中使用
应用场景
- JDK中的Calendar的getInstance方法
- JDBC中的Connection对象的获取
- Spring中IOC容器创建管理bean对象
- 反射中Class对象的newInstance方法
4. 建造者模式
- 定义:将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示
- 作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装就可以返回一辆完整的车辆!
// 没有指挥的时候
// 具体的产品是工人制作的,此时的工人有自由组合的能力,当然也是可以提供默认的套餐
public class Test {
public static void main(String[] args) {
// 服务员
Worker worker = new Worker();
Product product = worker.getProduct();
System.out.println(product.toString());
// 链式编程,在原来的基础上是可以自由组合的
System.out.println(worker.buildeA("鸡翅").getProduct().toString());
}
}
// 此时是存在指挥的时候,指挥来来决定具体实施操作工人的工作的流程
public class Test {
public static void main(String[] args) {
// 指挥
Director director = new Director();
// 指挥具体的工人,完成产品
Product bulid = director.bulid(new Worker());
System.out.println(bulid.toString());
}
}
5. 原型模式
简单理解起来就是不通过new这个关键字创建对象了,而是通过克隆的方式创建新的对象。这个在我们的spring中很常见的两种创建对象的方式,一个是单例模式,一个是原型模式。
// 视频
public class Video implements Cloneable{
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException {
// 此时实现的是浅克隆
// return super.clone();
// 实现深客隆的方式
Object o = super.clone();
Video v = (Video) o;
// 将对象的属性也要进行克隆
v.createTime = (Date) this.createTime.clone();
return o;
}
public Video() {
}
public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
// 客户端:需要实现我们的克隆
public class Bilibili {
public static void main(String[] args) throws CloneNotSupportedException {
// 原型对象
Date date = new Date();
Video video = new Video("飞飞",date);
System.out.println("video--->"+video);
System.out.println("video--->hashcode"+video.hashCode());
// v1克隆v2
Video video1 = (Video) video.clone();
System.out.println("video1--->"+video1);
System.out.println("video1--->hashcode"+video1.hashCode());
System.out.println("------------------");
video1.setName("clone2");
date.setTime(23333);
System.out.println("video--->"+video);
System.out.println("video--->hashcode"+video.hashCode());
System.out.println("video1--->"+video1);
System.out.println("video1--->hashcode"+video1.hashCode());
}
}
原型模式是和我们的克隆息息相关的,注意一下!
6. 适配器模式
适配器模式:将一个类的接口转换成为客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能在一起工作的那些类可以在一起工作了。
- 目标接口:客户所期待的接口,也可以是具体的或者抽象的类
对应的就是网线接口
- 需要适配的类:需要适配的类或者适配者类
电脑上的USB接口
- 适配器:通过包装一个需要适配的对象,把原接口转换成目标对象
网线USB转换器
// 需要被适配的类:网线
public class Adaptee {
public void request(){
System.out.println("连接网线上网!");
}
}
// 接口转换器的抽象实现
public interface NetToUSB {
// 作用:处理请求,网线-->usb
public void handlerRequest();
}
//真正的适配器,需要连接USB,连接网线
public class Adapter extends Adaptee implements NetToUSB{
@Override
public void handlerRequest() {
super.request();
}
}
// 比较好的还是通过使用组合的方式,而不是继承的方式
public class Adapter2 implements NetToUSB {
private Adaptee adaptee;
public Adapter2(Adaptee adaptee){
this.adaptee = adaptee;
}
@Override
public void handlerRequest() {
adaptee.request();
}
}
// 客户端类
public class Computer {
public void net(NetToUSB adapter){
// 上网的具体的实现
adapter.handlerRequest();
}
public static void main(String[] args) {
// 我们是需要电脑,适配器,网线三个东西才能上网
Computer computer = new Computer(); // 电脑
Adapter adapter = new Adapter(); // 适配器
Adaptee adaptee = new Adaptee(); // 网线
computer.net(adapter);
System.out.println("----------------------");
Computer computer1 = new Computer();
Adaptee adaptee1 = new Adaptee();
Adapter2 adapter2 = new Adapter2(adaptee1);
computer1.net(adapter2);
}
}
7. 桥接模式
桥接模式:将抽象部分与它的实现部分分离,使他们可以独立的变化,是一种对象结构性模式
桥接模式的应用场景
- java通过java虚拟机是吸纳了平台的无关性
- JDBC中的驱动程序也是桥接模式的应用
// 首先是我们存在品牌的概念,这也是第一维度
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 class 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 computer1 = new DeskTop(new Lenovo());
computer1.info();
}
}
8. 代理模式
8.1 静态代理
角色分析
- 抽象角色:一般使用接口或者抽象类来实现
- 真实角色:被代理的角色
- 代理角色:代理真实角色
- 客户:访问代理对象的人
代理模式的好处
- 可以使真实角色的操作更加纯粹,不需要关注其他的业务
- 公共的业务可以交给代理角色去做,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
缺点
- 每个真实的角色都需要一个代理角色,代码量会翻倍效率变得低了
// 租房子
public interface Rent {
void rent();
}
// 房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
// 房东的角色被代理了,中介的任务
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
fee();
hetong();
}
// 看房子
public void seeHouse(){
System.out.println("中介带你看房子!");
}
// 收中介费
public void fee(){
System.out.println("中介收中介费用!");
}
// 签合同
public void hetong(){
System.out.println("签合同!");
}
}
// 我们的客户直接是和中介打交道的
// 租客
public class Client {
public static void main(String[] args) {
Host host = new Host();
// host.rent();
// 代理
Proxy proxy = new Proxy(host);
proxy.seeHouse();
proxy.rent();
proxy.hetong();
}
}
8.2 动态代理
- 动态代理的底层都是反射
- 动态代理的角色和静态代理的角色是一样的
- 动态代理的代理类是动态生成的,不是直接写好的
- 动态代理可以分成两类
- 基于接口—JDK动态代理
- 基于类:cglib
- java字节码实现:javasist
了解两个类:Proxy,InvocationHandler
// 真是的房租
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
- Proxy这个类是用来生成动态代理类Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
- InvocationHandler这个类是用来处理代理的实例,并返回结果
// 使用这个类动态生成代理类
public class ProxyHandler implements InvocationHandler {
// 被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
// 生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
// 处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
Object result = method.invoke(rent, args);
fee();
hetong();
return result;
}
// 看房子
public void seeHouse(){
System.out.println("中介带你看房子!");
}
// 收中介费
public void fee(){
System.out.println("中介收中介费用!");
}
// 签合同
public void hetong(){
System.out.println("签合同!");
}
}
万能的动态代理类
// 使用这个类动态生成代理类
public class ProxyHandler implements InvocationHandler {
// 被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
// 处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
// 增加日志的功能
public void log(String msg){
System.out.println("执行了"+msg+"方法!");
}
}
动态代理除静态代理的好处之外,还有一个好处就是动态代理是可以代理一个接口的,只要是实现这个接口的类,我们都是可以代理的,这就大大减少了代码量,提高了效率!