内容参考:
23种设计模式详解
设计模式肝完了,还挺全!腾讯和阿里的offer已拿!
大话设计模式
设计模式的七大原则
开闭原则 | 对扩展开放,对修改关闭 | 降低维护带来的新风险 |
---|---|---|
依赖倒置原则 | 高层不应该依赖低层,要面向接口编程 | 更利于代码结构的升级扩展 |
单一职责原则 | 一个类只干一件事,实现类要单一 | 便于理解,提高代码的可读性 |
接口隔离原则 | 一个接口只干一件事,接口要精简单一 | 功能解耦,高聚合、低耦合 |
迪米特法则 | 不该知道的不要知道,一个类应该保持对其它对象最少的了解,降低耦合度 | 只和朋友交流,不和陌生人说话,减少代码臃肿 |
里氏替换原则 | 不要破坏继承体系,子类重写方法功能发生改变,不应该影响父类方法的含义 | 防止继承泛滥 |
合成复用原则 | 尽量使用组合或者聚合关系实现代码复用,少使用继承 | 降低代码耦合 |
设计模式的分类,你知道哪些设计模式?
创建型: 在创建对象的同时隐藏创建逻辑,不使⽤ new 直接实例化对象,程序在判断需要创建哪些对象时更灵活。包括工厂/抽象工厂/单例/建造者/原型模式。
结构型: 通过类和接⼝间的继承和引⽤实现创建复杂结构的对象。包括适配器/桥接模式/过滤器/组合/ 装饰器/外观/享元/代理模式。
行为型: 通过类之间不同通信方式实现不同行为。包括责任链/命名/解释器/迭代器/中介者/备忘录/观察者/状态/策略/模板/访问者模式。
说⼀说简单⼯⼚模式
简单工厂模式指由⼀个工厂对象来创建实例,客户端不需要关注创建逻辑,只需提供传⼊工厂的参数。
实现:咖啡抽象类,实体就是卡布奇诺、美式咖啡、拿铁之类的(让你写你就写)
⼯⼚⽅法模式
要什么就去工厂拿,只需要知道这个类名,不关心里面具体的实现。
创建的过程是在子类执行的,客户端只需要关心对应的什么工厂,而不用关心产品创建的细节,但是如果一个工厂要创建的产品很多,那么工厂太大也不容易维护。
- Collection 接⼝这个抽象工厂中定义了⼀个抽象的 工厂方法,返回⼀个 Iterator 类的抽象产品。
- Spring 的 FactoryBean 接⼝的⽅法也是工厂方法
抽象⼯⼚模式
抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建不同产品。
具体工厂:实现了抽象工厂创建产品的方法,完成具体产品的创建
抽象产品:定义了产品的规范,描述产品的特性和功能
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系
java.sql.Connection 接⼝就是⼀个抽象工厂,其中包括很多抽象产品如 Statement、Blob、Savepoint等。
单例模式的特点是什么?
⼀个单例类在任何情况下都只存在⼀个实例,因此构造方法必须是私有的、由自己创建⼀个静态变量存储实例,对外提供⼀个静态公有方法获取实例。
优点就是内存中只有一个实例,减少了开销,尤其是频繁的创建和销毁实例的情况,可以避免对资源的多重用。缺点就是难以扩展。
Spring 的 ApplicationContext 创建的 Bean 实例都是单例对象,还有 ServletContext、数据库连接池等也都是单例模式,还有什么工具类的实现,都是单例。
单例模式有哪些实现?
- 饿汉式:在类加载时就初始化创建单例对象,线程安全,但不管是否使⽤都创建对象可能会浪费内存。
public class SingletonEH {
private static SingletonEH instance = new SingletonEH();
private SingletonEH (){}
public static SingletonEH getInstance(){
return instance;
}
- 懒汉式:在外部调⽤时才会加载,线程不安全,可以加锁保证线程安全但效率低。
public class SingletonEH {
private static SingletonLH instance;
private SingletonLH (){}
public static SingletonLH getInstance(){
if(instance == null){
instance = new SingletonLH();
}
return instance;
}
- 双重检查锁:使⽤ volatile 以及多重检查来减⼩锁范围,提升效率。
public class SingletonSC{
private static volatile SingletonSC instance;
private SingletonSC(){}
public static SingletonSC getInstance(){
if(instance == null){
synchronized(SingletonSC.class){
instance = new SingletonSC();
}
}
return instance;
}
}
- 静态内部类:同时解决饿汉式的内存浪费问题和懒汉式的线程安全问题。
public class SingletonJT{
private SingletonJT(){}
private static class innerJT{
private static SingletonJT instance = new SingletonJT();
}
public SingletonJT getInstance(){
return innerJT.instance;
}
}
- 枚举:《Effective Java》提倡的⽅式,不仅能避免线程安全问题,还能防⽌反序列化重新创建新的对象,绝对防⽌多次实例化,也能防⽌反射破解单例的问题。
public enum SingletonEM{
INSTANCE;
public SingletonEM getInstance(){
return INSTANCE;
}
}
代理模式
代理模式属于结构型模式,为其他对象提供一种代理以控制对这个对象的访问。不仅拥有被代理人的功能,还能够额外的增加一些功能,因此通过增加一层来加强对象的功能。
package com.Model.Proxy;
//买车行为
public interface BuyCar {
public void buycar();
}
package com.Model.Proxy;
public class People implements BuyCar {
private int cash;
private String vip;
private String username;
@Override
public void buycar() {
System.out.println(username + " is vip so he/she can buy any car...");
}
public int getCash() {
return cash;
}
public void setCash(int cash) {
this.cash = cash;
}
public String getVip() {
return vip;
}
public void setVip(String vip) {
this.vip = vip;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
// 代理类 去检测买车行为是否符合规则
class ProxyBuyCar implements BuyCar {
private People People;
public People getPeople() {
return People;
}
public void setPeople(People people) {
People = people;
}
@Override
public void buycar() {
if (People.getVip().equals("vip")) {
People.buycar();
} else if (People.getCash() >= 50000) {
System.out.println(People.getUsername() + "buy a new car trade over...");
} else {
System.out.println(People.getUsername() + "people can't buy a car ");
}
}
}
分为静态代理和动态代理
静态代理:代理对象持有被代理对象的引⽤,代理了调用方法相当于调用引用的方法。
动态代理:动态代理在程序运⾏时通过反射创建具体的代理类,代理类和被代理类的关系在运⾏前是不确定的。动态代理的适⽤性更强,主要分为 JDK 动态代理和 CGLib 动态代理。
Spring 利⽤动态代理实现 AOP,如果 Bean 实现了接⼝就使⽤ JDK 代理,否则使⽤ CGLib 代理。
装饰者模式
装饰器模式属于结构型模式,在不改变原有对象的基础上将功能附加到对象,相⽐继承可以更加灵活地 扩展原有对象的功能。
装饰器模式适合的场景:在不想增加很多⼦类的前提下扩展⼀个类的功能。
java.io 包中,InputStream 字节输⼊流通过装饰器 BufferedInputStream 增强为缓冲字节输⼊流。
装饰者模式和动态代理的区别
装饰器模式的关注点在于给对象动态添加⽅法,⽽动态代理更注重对象的访问控制。
动态代理通常会在 代理类中创建被代理对象的实例,⽽装饰器模式会将装饰者作为构造⽅法的参数。
适配器模式
适配器模式属于结构型模式,它作为两个不兼容接⼝之间的桥梁,结合了两个独⽴接⼝的功能,将⼀个 类的接⼝转换成另外⼀个接⼝使得原本由于接⼝不兼容⽽不能⼀起⼯作的类可以⼀起⼯作。
缺点是过多使⽤适配器会让系统⾮常混乱,不易整体把握。
java.io 包中,InputStream 字节输⼊流通过适配器 InputStreamReader 转换为 Reader 字符输⼊流。
Spring MVC 中的 HandlerAdapter,由于 handler 有很多种形式,包括 Controller、HttpRequestHandler、Servlet 等,但调用方式⼜是确定的,因此需要适配器来进⾏处理,根据适配规则调⽤ handle 方法。
适配器模式和和装饰器模式以及代理模式的区别?
适配器模式没有层级关系,适配器和被适配者没有必然连续,满⾜ has-a 的关系,解决不兼容的问题, 是⼀种后置考虑。
装饰器模式具有层级关系,装饰器与被装饰者实现同⼀个接⼝,满⾜ is-a 的关系,注重覆盖和扩展,是⼀种前置考虑。
策略模式
策略模式主要解决在有多种算法相似的情况下,使⽤ if/else 所带来的难以维护。可以避免使用多重条件判断并且扩展性良好,缺点是策略类会增多并且所有 策略类都需要对外暴露。
/\* \* 声明旅行 \*/
interface ITrip {
void going();
}
class Bike implements ITrip {
@Override
public void going() {
System.out.println("骑自行车");
}
}
class Drive implements ITrip {
@Override
public void going() {
System.out.println("开车");
}
}
/\* \* 定义出行类 \*/
class Trip {
private ITrip trip;
public Trip(ITrip trip) {
this.trip = trip;
}
public void doTrip() {
this.trip.going();
}
}
/\* \* 执行方法 \*/
public class StrategyTest {
public static void main(String[] args) {
Trip trip = new Trip(new Bike());
trip.doTrip();
}
}
模版模式
就像一套试卷和多个考生
- 考生:向答题框填答案
- 试卷:每个试卷都是一样的,问答题+答题框
其实这里就涉及到了不变的和可变的行为混杂一起了,目的就是抽取出不变的放在父类中,变化的就延迟在子类中。
优点是可以封装固定不变的部分,扩展可变的部分。
缺点是每⼀个不同实现都需要⼀个⼦类维护,会增加类的数量。
HttpServlet 定义了⼀套处理 HTTP 请求的模板,service ⽅法为模板⽅法,定义了处理HTTP请求的基本流程,doXXX 等⽅法为基本⽅法,根据请求⽅法的类型做相应的处理,⼦类可重写这些⽅法。
观察者模式
观察者模式属于⾏为型模式,也叫发布订阅模式,定义对象间的⼀种⼀对多的依赖关系,当⼀个对象的 状态发⽣改变时,所有依赖于它的对象都得到通知并被⾃动更新。
主要解决⼀个对象状态改变给其他对象通知的问题
优点: 1、观察者和被观察者是抽象耦合的(因为他们是一套的,因此改变一个也需要改变对应的)。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
ServletContextListener 能够监听 ServletContext 对象的⽣命周期,实际上就是监听 Web 应⽤。当Servlet 容器启动 Web 应⽤时调⽤contextInitialized方法,终⽌时调⽤ contextDestroyed方法。