设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
- 设计模式的意义:
- 提高程序员思维能力、编程能力和设计能力。
- 是程序更加标准化、代码编制工程化,提高效率,缩短开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、可维护性强。
- 创建型模式
- 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
- 结构型模式
- 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
- 行为模式
- 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
OOP七大原则
- 开闭原则:对扩展开放,对修改关闭。
- 里氏替换原则:继承必须确保超类所拥有的的性质在子类中仍然成立。
- 依赖倒置原则::要面向接口编程,不要面向实现编程。
- 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。
- 接口隔离原则:为各个类建立他们需要而专用接口。
- 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话。
- 合成复用原则:尽量使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
创建型 --- 单例模式
单例模式指在某个系统中一个类只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。
单例模式思想 构造器私有
饿汉式单例模式
饿汉模式,即在初始阶段就主动进行实例化,并时刻保持一种渴求的状态,无论此单例是否有人使用。
// 饿汉式单例模式
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(){ // 开放一个对外的方法 获取HUNGRY
return HUNGRY;
}
}
懒汉式单例模式 要用的时候再去加载
饿汉模式存在没有必要的消耗,浪费空间。基于这种考虑,于是就有了懒汉模式,也就是在需要的时候再对单例进行实例化
public class LazyMan{
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
单线程下单例是可以的 多线程并发场景下会有问题
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();
}
}
}
双重检测锁模式的懒汉式单例 简称DCL懒汉式
public class LazyMan_DCL{
private LazyMan_DCL(){
}
private static LazyMan_DCL lazyMan;
public static LazyMan_DCL getInstance(){
if(lazyMan == null){
synchronized (LazyMan_DCL.class){
if(lazyMan == null){
lazyMan = new LazyMan_DCL();
}
}
}
return lazyMan;
}
}
但是存在指令重排的问题
public static LazyMan_DCL getInstance(){
if(lazyMan == null){
synchronized (LazyMan_DCL.class){
if(lazyMan == null){
lazyMan = new LazyMan_DCL(); //不是原子性操作 在极端情况下还是会出现问题的
/**
* 1. 分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 会发生指令重排现象
* 123
* 变成 132
*/
}
}
}
return lazyMan; //有可能因为指令重排导致返回null对象
加上volatile关键字防止指令重排
public class LazyMan_DCL{
private LazyMan_DCL(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static LazyMan_DCL lazyMan; //所以因为下面的涉及指令重排的问题 需要加上volatile关键字
public static LazyMan_DCL getInstance(){
if(lazyMan == null){
synchronized (LazyMan_DCL.class){
if(lazyMan == null){
lazyMan = new LazyMan_DCL(); //不是原子性操作 在极端情况下还是会出现问题的
/**
* 1. 分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 会发生指令重排现象
* 123
* 变成 132
*/
}
}
}
return lazyMan; //有可能因为指令重排导致返回null对象
}
}
相比“懒汉模式”,其实在大多数情况下我们通常会更多地使用“饿汉模式”,原因在于这个单例迟早是要被实例化占用内存的,延迟懒加载的意义并不大,加锁解锁反而是一种资源浪费,同步更是会降低CPU的利用率,使用不当的话反而会带来不必要的风险。越简单的包容性越强,而越复杂的反而越容易出错。
核心作用
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
常见场景
项目中,读取配置文件的类,一般也只有一个对象,没必要每次都去new对象读取网站的计数器一般也会采用单例模式,可以保证同步
数据库连接池的设计一般也是单例模式
在Servlet编程中,每个Servlet也是单例的
在Spring中,每个Bean默认就是单例的
创建型 --- 工厂模式
实现创建者和调用者的分离
核心本质:
- 实例化对象不适用new, 用工厂方法代替
- 将选择实现类,创建对象统一管理和控制,从而将调用者跟我们的实现类解耦
三种模式:
- 简单工厂模式
- 用来生产同一等级结构中的任何产品(对于增加新的产品,需要扩展已有代码)
- 工厂方法模式
- 用来生产同一等级结构中的固定产品(支持增加任意产品)
- 抽象工厂模式
- 围绕一个超级工厂创建其他工厂,该超级工厂又被称为其他工厂的工厂
简单工厂模式
定义一个接口
public interface Car
{
void name();
}
接口的实现类
public class WuLing implements Car
{
@Override
public void name()
{
System.out.println("五菱宏光!");
}
}
public class Model implements Car
{
@Override
public void name()
{
System.out.println("特斯拉!");
}
}
简单工厂
// 静态工厂模式
public class CarFactory
{
// 方法一 违反了开闭原则
public static Car getCar(String car)
{
if ("五菱".equals(car))
{
return new WuLing();
}
else if ("特斯拉".equals(car))
{
return new Model();
}
else
{
return null;
}
}
// 方法二
public static Car getWuLing()
{
return new WuLing();
}
public static Car getModel()
{
return new Model();
}
}
输出
public class Consumer
{
public static void main(String[] args)
{
// 正常方法
WuLing wuLing = new WuLing();
Model model = new Model();
wuLing.name();
model.name();
// 使用工厂创建 方法一
Car wuLingF = CarFactory.getCar("五菱");
wuLingF.name();
Car modelF = CarFactory.getCar("特斯拉");
modelF.name();
// 方法二
Car wuLing2 = CarFactory.getWuLing();
Car model2 = CarFactory.getModel();
wuLing2.name();
model2.name();
}
}
违反了开闭原则
工厂方法模式
创建工厂方法
// 工厂方法模式
public interface CarFactory
{
Car getCar();
}
public class WuLingFactory implements CarFactory
{
@Override
public Car getCar()
{
return new WuLing();
}
}
public class ModelFactory implements CarFactory
{
@Override
public Car getCar()
{
return new Model();
}
}
输出
public class Consumer
{
public static void main(String[] args)
{
Car car_WuLing = new WuLingFactory().getCar();
Car car_Model = new ModelFactory().getCar();
car_WuLing.name();
car_Model.name();
Car car_xiaoPeng = new XiaoPengFactory().getCar();
car_xiaoPeng.name();
}
}
结构型 --- 代理模式
Spring AOP底层 应用了动态代理
现实的案例
静态代理
抽象角色 租房接口
//租房
public interface Rent {
public void rent();
}
真实角色 房东
//房东
public class Host implements Rent {
public void rent() {
System.out.println("房东要出租房子!");
}
}
代理角色 中介
public class Proxy implements Rent {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void rent() {
seeHouse();
host.rent();
hetong();
fare();
}
//看房
public void seeHouse(){
System.out.println("中介带你看房");
}
//看房
public void hetong(){
System.out.println("签租赁合同");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
客户端访问代理角色 租客
public class Client {
public static void main(String[] args) {
//房东要租房子
Host host = new Host();
//代理,中介帮房东租房子,但是呢?代理角一般会有一些附属操作!
Proxy proxy = new Proxy(host);
//你不用面对房东,直接找中介租房即可!
proxy.rent();
}
}
代理模式的好处
1. 可以使真实角色的操作更加纯粹,不用去关心一些公共业务
2. 公共业务就交给代理角色, 实现业务的分工
2. 公共业务发送扩展的时候,方便集中管理
缺点:
1. 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率变低
另一个静态代理的实例
接口
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("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
//1.改动原有的业务代码,在公司中是大忌!
}
代理对象
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.add();
}
public void update() {
log("update");
userService.add();
}
public void query() {
log("query");
userService.add();
}
//日志方法
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.query();
}
}
动态代理
1. 动态代理和静态代理的角色一致
2. 动态代理使用反射动态生成代理类,来修正静态代理的缺点。 静态代理的代理类需要手动实现
3. 动态代理分为两大类:基于接口的动态代理 和 基于类的动态代理
基于接口的动态代理----- JDK动态代理
基于类的动态代理 ---- cglib
java字节码实现
JDK动态代理
需要了解两个类 Proxy: 代理 InvocationHandler: 调用处理程序
Proxy类: 代理
Proxy提供了创建动态代理类和实例的静态方法
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
InvocationHandler h)
InvocationHandler: 调用处理程序
只有一个invoke方法
抽象角色 租房接口
//租房
public interface Rent {
public void rent();
}
真实角色 房东
//房东
public class Host implements Rent {
public void rent() {
System.out.println("房东要出租房子!");
}
}
动态生成代理对象
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//等我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
/**
* 动态代理有两种:
* 1.jdk动态代理 要求必须有接口,最终生成的代理类和目标类实现相同的接口
* 最终生成的代理类在com.sun.proxy包下,类名为$proxy2
* 2.cglib动态代理 最终生成的代理类会继承目标类,并且和目标类在相同的包下
*/
/**
* Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。
* 这个方法一共有 3 个参数:
* loader :类加载器,用于加载代理对象。 指定加载动态生成的代理类的类加载器
* interfaces : 被代理类实现的一些接口; 代理类和目标类实现相同的接口 获取目标对象实现的所有接口的class对象的数组
* h : 实现了 InvocationHandler 接口的对象; 执行处理 代理类是动态生成的 代理类如何去重写抽象方法 设置代理类的抽象方法如何重写
*/
//生成任意目标所需要的得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
/**
* 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。
*
* * 当你使用代理对象调用方法的时候实际会调用到这个方法 method.invoke(rent, args);
*/
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy代表代理对象 method表示要执行的方法 args要执行的方法的参数列表
//动态代理的本质,就是使用反射机制实现!
seeHouse(); //功能实现之前
Object result = method.invoke(rent, args); //目标对象实现功能的过程
fare(); //功能实现之后
return result;
}
public void seeHouse(){
System.out.println("中介带看房子");
}
public void fare(){
System.out.println("收中介费");
}
}
访问代理对象
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就是动态生成的,我们并没有写~
proxy.rent();
}
}
结构型 --- 适配器模式
以现实生活中的例子为例
将一个类的接口转换成客户希望的另外一个接口Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作!
1. 类适配器模式(通过继承, 单继承)
网线
// 要被适配的类: 网线
public class WebLine {
public void connect(){
System.out.println("连接网线上网");
}
}
电脑
// 客户端类 想上网 但是插不上网线
public class Computer {
// 我们电脑需要连接上转接器才能上网
public void net(NetToUsb adapter){
// 上网的具体实现 找一个转接头
adapter.handleRequest();
}
public static void main(String[] args) {
// 电脑 适配器 网线
WebLine adaptee = new WebLine();
Adapter adapter = new Adapter();
Computer computer = new Computer();
computer.net(adapter);
}
}
接口
// 接口转换器的抽象实现
public interface NetToUsb {
//作用 处理请求 网线 --> usb
public void handleRequest();
}
适配器
// 真正的适配器 需要连接usb 连接网线
public class Adapter extends WebLine implements NetToUsb {
@Override
public void handleRequest() {
super.connect(); // 可以上网了
}
}
2. 对象适配器 组合的方式(常用)
// 真正的适配器 需要连接usb 连接网线
public class Adapter2 implements NetToUsb {
private WebLine webLine;
public Adapter2(WebLine webLine) {
this.webLine = webLine;
}
@Override
public void handleRequest() {
webLine.connect(); // 可以上网了
}
}
// 客户端类 想上网 但是插不上网线
public class Computer {
// 我们电脑需要连接上转接器才能上网
public void net(NetToUsb adapter){
// 上网的具体实现 找一个转接头
adapter.handleRequest();
}
public static void main(String[] args) {
// 电脑 适配器 网线
WebLine WebLine = new WebLine();
Adapter2 adapter = new Adapter2(WebLine);
Computer computer = new Computer();
computer.net(adapter);
}
}
优点
1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用。
3、增加了类的透明度。
4、灵活性好。
缺点
1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
2、由于 Java 至多继承一个类,所以至多只能适配一个类,而且目标类必须是抽象类。
结构型 --- 装饰器模式
装饰器模式能够在运行时动态地为原始对象增加一些额外的功能,使其变得更加强大。
从某种程度上讲,装饰器非常类似于“继承”,它们都是为了增强原始对象的功能,
区别在于方式的不同,后者是在编译时静态地通过对原始类的继承完成,而前者则是在程序运行时通过对原始对象动态地“包装”完成,是对类实例(对象)“装饰”的结果。
1.实体类
- Gift类和Showable接口
package com.wang.design.chapter7;
public interface Showable {
void show();//展示礼物的方法
}
class Gift implements Showable{
@Override
public void show() {
System.out.print("礼物");
}
}
2.装饰器
- 用来包装Gift的包装器体系
package com.wang.design.chapter7;
/**
* Decorator为包装器父类
*/
public abstract class Decorator implements Showable{
protected Showable show_obj;
public Decorator(Showable show_obj){
this.show_obj=show_obj;//注入被装饰对象
}
@Override
public void show() {
show_obj.show();//调用默认展示方法
}
}
/**
* 包装盒
*/
class BoxDecorator extends Decorator{
public BoxDecorator(Showable show_obj){
super(show_obj);
}
@Override
public void show() {
System.out.print("包装盒【");
show_obj.show();
System.out.print("】包装盒");
}
}
/**
* 蝴蝶结
*/
class BowknotDecorator extends Decorator{
public BowknotDecorator(Showable show_obj){
super(show_obj);
}
@Override
public void show() {
System.out.print("蝴蝶结【");
show_obj.show();
System.out.print("】蝴蝶结");
}
}
- 客户端展示
package com.wang.design.chapter7;
public class Client {
public static void main(String[] args) {
Showable gift=new BowknotDecorator(new BoxDecorator(new Gift()));
gift.show();
}
}
到这里,我们通过低耦合、高扩展的设计模式,成功实现了对一个类的包装。
行为型 --- 模板方法模式
模板方法模式非常类似于定制表格,设计者先将所有需要填写的信息头(字段名)抽取出来,再将它们整合在一起成为一种既定格式的表格,最后让填表人按照这个标准化模板去填写自己特有的信息,而不必为书写内容、先后顺序、格式而感到困扰。
具体地说,就是把所有子类通用的信息和行为抽象出来放在父类中,建立抽象方法或非抽象的通用方法,然后由子类去继承和实现。下面我以[上数学课]和[上英语课]为例,展示模板方法模式的代码实现。
- 实体类
package com.wang.design.chapter12;
public abstract class Course {
abstract void register();//选课
abstract void homework();//做作业
abstract void exam();//考试
public void show(){//通用方法
this.register();
this.homework();
this.exam();
}
}
class Math extends Course{
@Override
void register() {
System.out.println("=====数学课开课了=====");
}
@Override
void homework() {
System.out.println("=====完成数学课平时=====");
}
@Override
void exam() {
System.out.println("=====进行数学期末考试=====");
}
}
class English extends Course{
@Override
void register() {
System.out.println("=====英语课开课了=====");
}
@Override
void homework() {
System.out.println("=====完成英语课平时=====");
}
@Override
void exam() {
System.out.println("=====进行英语期末考试=====");
}
}
- 客户端测试一下
package com.wang.design.chapter12;
public class Client {
public static void main(String[] args) {
Course course=new Math();
course.show();
course=new English();
course.show();
}
}
行为型 --- 观察者模式
观察者模式可以针对被观察对象与观察者对象之间一对多的依赖关系建立起一种行为自动触发机制,当被观察对象状态发生变化时主动对外发起广播,以通知所有观察者做出响应。
在一般情况下,观察者总是处于非常忙碌的状态,因为它总是在一遍又一遍地轮询被观察者,直到被观察者的状态发生变化。这样做无疑会给系统带来很多额外的负担。观察者模式则反其道而行之,与其让观察者无休止地询问,不如让被观察者在状态发生变化之后,主动通知观察者前来访问。
本章以[商店卖货]为例。
- 杂货铺
public class Shop {
private String name;//商店名
private List<String> handicraft,stationary,phone;//售卖种类:手工艺品、文具、手机
private List<Buyer> buyers;//买家预定
public Shop(String name){
this.name=name;
this.handicraft=new ArrayList<>();
this.stationary=new ArrayList<>();
this.phone=new ArrayList<>();
buyers=new ArrayList<>();
System.out.println(this.name+"开张了!");
}
public List<String> getHandicraft() {
return handicraft;
}
public List<String> getStationary() {
return stationary;
}
public List<String> getPhone() {
return phone;
}
//商家登记
public void register(Buyer buyer){
this.buyers.add(buyer);
}
//进货
public void purchaseHandicraft(String product){
this.handicraft.add(product);
notifyBuyers();
}
public void purchaseStationary(String product){
this.stationary.add(product);
notifyBuyers();
}
public void purchasePhone(String product){
this.phone.add(product);
notifyBuyers();
}
//通知买家
public void notifyBuyers(){
this.buyers.stream().forEach(b->b.inform(this));
}
}
- 买家类
public abstract class Buyer {
protected String name;
public Buyer(String name) {
this.name = name;
}
//来自商家的到货通知
public abstract void inform(Shop shop);
}
class HandicraftBuyer extends Buyer{
public HandicraftBuyer(String name) {
super(name);
}
@Override
public void inform(Shop shop) {
List<String> products=shop.getHandicraft();
if(products.size()!=0){
System.out.println("====="+this.name+"买入手工艺品=====");
for (String str:products){
System.out.println(str);
}
products.clear();
}
}
}
class StatonaryBuyer extends Buyer{
public StatonaryBuyer(String name) {
super(name);
}
@Override
public void inform(Shop shop) {
List<String> products=shop.getStationary();
if(products.size()!=0){
System.out.println("====="+this.name+"买入文具=====");
for (String str:products){
System.out.println(str);
}
products.clear();
}
}
}
class PhoneBuyer extends Buyer{
public PhoneBuyer(String name) {
super(name);
}
@Override
public void inform(Shop shop) {
List<String> products=shop.getPhone();
if(products.size()!=0){
System.out.println("====="+this.name+"买入手机=====");
for (String str:products){
System.out.println(str);
}
products.clear();
}
}
}
- 客户端测试
public class Client {
public static void main(String[] args) {
Shop shop=new Shop("解忧杂货铺");
Buyer handyBuyer=new HandicraftBuyer("手工艺品买手");
Buyer phoneBuyer=new PhoneBuyer("手机买手");
Buyer stationaryBuyer=new StatonaryBuyer("文具买手");
//买家登记
shop.register(handyBuyer);
shop.register(phoneBuyer);
shop.register(stationaryBuyer);
//到货了
shop.purchaseHandicraft("兵马俑摆件");
shop.purchaseHandicraft("海螺捕梦网");
shop.purchaseHandicraft("古风沙漏");
shop.purchaseStationary("直尺");
shop.purchaseStationary("书立");
shop.purchaseStationary("订书机");
shop.purchasePhone("华为荣耀20");
shop.purchasePhone("华为荣耀20s");
}
}
解忧杂货铺开张了!
=====手工艺品买手买入手工艺品=====
兵马俑摆件
=====手工艺品买手买入手工艺品=====
海螺捕梦网
=====手工艺品买手买入手工艺品=====
古风沙漏
=====文具买手买入文具=====
直尺
=====文具买手买入文具=====
书立
=====文具买手买入文具=====
订书机
=====手机买手买入手机=====
华为荣耀20
=====手机买手买入手机=====
华为荣耀20s
Process finished with exit code 0