本文旨在快速梳理经常使用的设计模式,了解每一个模式主要针对的是哪些状况以及其基础特征,每一个模式前都有列举出一个或多个能够深刻阅读的参考网页,以供读者详细了解其实现。html
分为三篇文章:java
全复习手册文章导航git
点击公众号下方:技术推文——面试冲刺github
建立型面试
首先搞清楚一点,设计模式不是高深技术,不是奇淫技巧。设计模式只是一种设计思想,针对不一样的业务场景,用不一样的方式去设计代码结构,其最最本质的目的是为了解耦,延伸一点的话,还有为了可扩展性和健壮性,可是这都是创建在解耦的基础之上。算法
高内聚:系统中A、B两个模块进行交互,若是修改了A模块,不影响模块B的工做,那么认为A有足够的内聚。sql
低耦合:就是A模块与B模块存在依赖关系,那么当B发生改变时,A模块仍然能够正常工做,那么就认为A与B是低耦合的。apache
请参考Github详细解释,下面的一点仅供快速复习。Github写的很好。设计模式
同时参考:api
确保一个类只有一个实例,并提供该实例的全局访问点。
使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能经过构造函数来建立对象实例,只能经过公有静态函数返回惟一的私有静态变量。
一.私有化构造函数
二.声明静态单例对象
三.构造单例对象以前要加锁(lock一个静态的object对象)或者方法上加synchronized。
四.须要两次检测单例实例是否已经被构造,分别在锁以前和锁以后
使用lock(obj)
public class Singleton {
private Singleton() {} //关键点0:构造函数是私有的
private volatile static Singleton single; //关键点1:声明单例对象是静态的
private static object obj= new object();
public static Singleton GetInstance() //经过静态方法来构造对象
{
if (single == null) //关键点2:判断单例对象是否已经被构造
{
lock(obj) //关键点3:加线程锁
{
if(single == null) //关键点4:二次判断单例是否已经被构造
{
single = new Singleton();
}
}
}
return single;
}
}
复制代码
使用synchronized (Singleton.class)
public class Singleton {
private Singleton() {}
private volatile static Singleton uniqueInstance;
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
复制代码
0.为什么要检测两次?
若是两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在if语句块内有加锁操做,可是两个线程都会执行 uniqueInstance = new Singleton(); 这条语句,只是前后的问题,也就是说会进行两次实例化,从而产生了两个实例。所以必须使用双重校验锁,也就是须要使用两个 if 语句。
1.构造函数可否公有化?
不行,单例类的构造函数必须私有化,单例类不能被实例化,单例实例只能静态调用。
2.lock住的对象为何要是object对象,能够是int吗?
不行,锁住的必须是个引用类型。若是锁值类型,每一个不一样的线程在声明的时候值类型变量的地址都不同,那么上个线程锁住的东西下个线程进来会认为根本没锁。
3.uniqueInstance 采用 volatile 关键字修饰
uniqueInstance = new Singleton(); 这段代码实际上是分为三步执行。
分配内存空间
初始化对象
将 uniqueInstance 指向分配的内存地址
复制代码
可是因为 JVM 具备指令重排的特性,有可能执行顺序变为了 1>3>2
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton(){}
public static Singleton getInstance(){
if(uniqueInstance == null){
// B线程检测到uniqueInstance不为空
synchronized(Singleton.class){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
// A线程被指令重排了,恰好先赋值了;但还没执行完构造函数。
}
}
}
return uniqueInstance;// 后面B线程执行时将引起:对象还没有初始化错误。
}
}
复制代码
线程不安全问题主要是因为 uniqueInstance 被实例化了屡次,若是 uniqueInstance 采用直接实例化的话,就不会被实例化屡次,也就不会产生线程不安全问题。可是直接实例化的方式也丢失了延迟实例化带来的节约资源的优点。
private static Singleton uniqueInstance = new Singleton();
复制代码
当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance()
方法从而触发 SingletonHolder.INSTANCE
时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。
这种方式不只具备延迟初始化的好处,并且由虚拟机提供了对线程安全的支持。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
复制代码
这是单例模式的最佳实践,它实现简单,而且在面对复杂的序列化或者反射攻击的时候,可以防止实例化屡次。
public enum Singleton {
uniqueInstance;
}
复制代码
考虑如下单例模式的实现,该 Singleton 在每次序列化的时候都会建立一个新的实例,为了保证只建立一个实例,必须声明全部字段都是 transient,而且提供一个 readResolve() 方法。
public class Singleton implements Serializable {
private static Singleton uniqueInstance;
private Singleton() {
}
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
复制代码
若是不使用枚举来实现单例模式,会出现反射攻击,由于经过 setAccessible() 方法能够将私有构造函数的访问级别设置为 public,而后调用构造函数从而实例化对象。若是要防止这种攻击,须要在构造函数中添加防止实例化第二个对象的代码。
从上面的讨论能够看出,解决序列化和反射攻击很麻烦,而枚举实现不会出现这两种问题,因此说枚举实现单例模式是最佳实践。
在建立一个对象时不向客户暴露内部细节,并提供一个建立对象的通用接口。
在简单工厂模式中,能够根据参数的不一样返回不一样类的实例。
简单工厂模式专门定义一个类来负责建立其余类的实例
简单工厂模式包含以下角色:
Factory:工厂角色 工厂角色负责实现建立全部实例的内部逻辑
Product:抽象产品角色 抽象产品角色是所建立的全部对象的父类,负责描述全部实例所共有的公共接口
ConcreteProduct:具体产品角色 具体产品角色是建立目标,全部建立的对象都充当这个角色的某个具体类的实例。
public class Test {
public static void main(String[] args) {
String loginType = "password";
String name = "name";
String password = "password";
Login login = LoginManager.factory(loginType);
boolean bool = login.verify(name, password);
if (bool) {
/**
* 业务逻辑
*/
} else {
/**
* 业务逻辑
*/
}
}
}
复制代码
构造容易,逻辑简单。
1.简单工厂模式中的if else判断很是多,彻底是Hard Code,若是有一个新产品要加进来,就要同时添加一个新产品类,而且必须修改工厂类,再加入一个 else if 分支才能够, 这样就违背了 “开放-关闭原则“中的对修改关闭的准则了。
2.一个工厂类中集合了全部的类的实例建立逻辑,违反了高内聚的责任分配原则,将所有的建立逻辑都集中到了一个工厂类当中,全部的业务逻辑都在这个工厂类中实现。何时它不能工做了,整个系统都会受到影响。所以通常只在很简单的状况下应用,好比当工厂类负责建立的对象比较少时。
3.简单工厂模式因为使用了静态工厂方法,形成工厂角色没法造成基于继承的等级结构。
工厂类负责建立的对象比较少:因为建立的对象较少,不会形成工厂方法中的业务逻辑太过复杂。
①JDK类库中普遍使用了简单工厂模式,如工具类java.text.DateFormat,它用于格式化一个本地日期或者时间。
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale
locale);
复制代码
②Java加密技术 获取不一样加密算法的密钥生成器:
KeyGenerator keyGen=KeyGenerator.getInstance("DESede");
复制代码
建立密码器:
Cipher cp = Cipher.getInstance("DESede");
复制代码
又称为工厂模式/虚拟构造器(Virtual Constructor)模式/多态工厂(Polymorphic Factory)模式
即经过工厂子类来肯定究竟应该实例化哪个具体产品类。
再也不设计一个按钮工厂类来统一负责全部产品的建立,而是将具体按钮的建立过程交给专门的工厂子类去完成。
咱们先定义一个抽象的按钮工厂类,再定义具体的工厂类来生成圆形按钮、矩形按钮、菱形按钮等,它们实如今抽象按钮工厂类中定义的方法。这种抽象化的结果使这种结构能够在不修改具体工厂类的状况下引进新的产品,若是出现新的按钮类型,只须要为这种新类型的按钮建立一个具体的工厂类就能够得到该新按钮的实例,这一特色无疑使得工厂方法模式具备超越简单工厂模式的优越性,更加符合“开闭原则”。
Product:抽象产品,工厂方法模式所建立的对象的超类,也就是全部产品类的共同父类或共同拥有的接口。在实际的系统中,这个角色也经常使用抽象类实现。
ConcreteProduct:具体产品,这个角色实现了抽象产品(Product)所声明的接口,工厂方法模式所建立的每个对象都是某个具体产品的实例。
Factory:抽象工厂,担任这个角色的是工厂方法模式的核心,任何在模式中建立对象的工厂类必须实现这个接口。在实际的系统中,这个角色也经常使用抽象类实现。
ConcreteFactory:具体工厂,担任这个角色的是实现了抽象工厂接口的具体Java类。具体工厂角色含有与业务密切相关的逻辑,而且受到使用者的调用以建立具体产品对象。
参考连接内有详细实现
客户端调用
static void Main(string[] args)
{
//先给我来个灯泡
ICreator creator = new BulbCreator();
ILight light = creator.CreateLight();
light.TurnOn();
light.TurnOff();
//再来个灯管看看
creator = new TubeCreator();
light = creator.CreateLight();
light.TurnOn();
light.TurnOff();
}
复制代码
①在工厂方法模式中,工厂方法用来建立客户所须要的产品,同时还向客户隐藏了哪一种具体产品类将被实例化这一细节,用户只须要关心所需产品对应的工厂,无须关心建立细节,甚至无须知道具体产品类的类名。
②工厂方法模式之因此又被称为多态工厂模式,是由于全部的具体工厂类都具备同一抽象父类。
③使用工厂方法模式的另外一个优势是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其余的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就能够了。这样,系统的可扩展性也就变得很是好,彻底符合“开闭原则”,这点比简单工厂模式更优秀。
①在添加新产品时,须要编写新的具体产品类,并且还要提供与之对应的具体工厂类,系统中类的个数将成对增长,在必定程度上增长了系统的复杂度,有更多的类须要编译和运行,会给系统带来一些额外的开销。
②因为考虑到系统的可扩展性,须要引入抽象层,在客户端代码中均使用抽象层进行定义,增长了系统的抽象性和理解难度,且在实现时可能须要用到DOM、反射等技术,增长了系统的实现难度。
Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=DB;user=sa;password=");
Statement statement=conn.createStatement();
ResultSet rs=statement.executeQuery("select * from UserInfo");
复制代码
产品等级结构 :产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不一样产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。
抽象工厂模式是全部形式的工厂模式中最为抽象和最具通常性的一种形态。
工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则须要面对多个产品等级结构,一个工厂等级结构能够负责多个不一样产品等级结构中的产品对象的建立 。
抽象工厂模式包含以下角色:
抽象产品: 苹果系列
public interface Apple
{
void AppleStyle();
}
复制代码
具体产品:iphone
public class iphone implements Apple
{
public void AppleStyle()
{
Console.WriteLine("Apple's style: iPhone!");
}
}
复制代码
抽象工厂
public interface Factory
{
Apple createAppleProduct();
Sumsung createSumsungProduct();
}
复制代码
手机工厂
public class Factory_Phone implements Factory
{
public Apple createAppleProduct()
{
return new iphone();
}
public Sumsung createSumsungProduct()
{
return new note2();
}
}
复制代码
调用
public static void Main(string[] args) {
//采购商要一台iPad和一台Tab
Factory factory = new Factory_Pad();
Apple apple = factory.createAppleProduct();
apple.AppleStyle();
Sumsung sumsung = factory.createSumsungProduct();
sumsung.BangziStyle();
//采购商又要一台iPhone和一台Note2
factory = new Factory_Phone();
apple = factory.createAppleProduct();
apple.AppleStyle();
sumsung = factory.createSumsungProduct();
sumsung.BangziStyle();
}
复制代码
①应用抽象工厂模式能够实现高内聚低耦合的设计目的,所以抽象工厂模式获得了普遍的应用。
②增长新的具体工厂和产品族很方便,由于一个具体的工厂实现表明的是一个产品族,无须修改已有系统,符合“开闭原则”。
开闭原则的倾斜性(增长新的工厂和产品族容易,增长新的产品等级结构麻烦)
在如下状况下可使用抽象工厂模式:
①一个系统不该当依赖于产品类实例如何被建立、组合和表达的细节,这对于全部类型的工厂模式都是重要的。
②系统中有多于一个的产品族,而每次只使用其中某一产品族。(与工厂方法模式的区别)
③属于同一个产品族的产品将在一块儿使用,这一约束必须在系统的设计中体现出来。
④系统提供一个产品类的库,全部的产品以一样的接口出现,从而使客户端不依赖于具体实现。
封装一个对象的构造过程,并容许按步骤构造。
实现代码见参考连接
一水杯工厂要生产各式各样的水杯,不管杯子是神马造型,但都包括绳子,帽子和杯体。以此模型建立各类类型的杯子。
经过一个已经存在的对象,复制出更多的具备与此对象具备相同类型的新的对象。
咱们知道,一个类的定义中包括属性和方法。属性用于表示对象的状态,方法用于表示对象所具备的行为。其中,属性既能够是Java中基本数据类型,也能够是引用类型。
Java中的浅复制一般使用 clone() 方式完成。
当进浅复制时,clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不一样的堆空间。同时,复制出来的对象具备与原对象一致的状态。
此处对象一致的状态是指:复制出的对象与原对象中的属性值彻底相等==。
Java中的深复制通常是经过对象的序列化和反序列化得以实现。序列化时,须要实现Serializable接口。
从输出结果中能够看出,深复制不只在堆内存上开辟了空间以存储复制出的对象,甚至连对象中的引用类型的属性所指向的对象也得以复制,从新开辟了堆空间存储。
更多精彩文章,请查阅个人博客或关注个人公众号:Rude3Knife
全复习手册文章导航
点击公众号下方:技术推文——面试冲刺
知识点复习手册文章推荐
我是蛮三刀把刀,目前为后台开发工程师。主要关注后台开发,网络安全,Python爬虫等技术。
来微信和我聊聊:yangzd1102
Github:github.com/qqxx6661
同步更新如下博客
1. Csdn
拥有专栏:Leetcode题解(Java/Python)、Python爬虫开发、面试助攻手册
2. 知乎
拥有专栏:码农面试助攻手册
3. 掘金
4. 简书
若是文章对你有帮助,不妨收藏起来并转发给您的朋友们~