共计 8309 个字符,预计需要花费 21 分钟才能阅读完成。
(一) 单例模式
单例模式,在它的外围构造中只蕴含一个被称为单例的非凡类。通过单例模式能够保证系统中一个类只有一个实例。
特点:
1、单例类只能有一个实例。
2、单例类必须本人创立本人的惟一实例。
3、单例类必须给所有其余对象提供这一实例
单例模式的要点:
1,公有的构造方法
2,指向本人实例的公有动态援用
3,以本人实例为返回值的动态的私有的办法
单例模式依据实例化对象机会的不同分为两种:
一种是饿汉式单例,一种是懒汉式单例。
饿汉式单例在单例类被加载时候,就实例化一个对象交给本人的援用;而懒汉式在调用获得实例办法的时候才会实例化对象。
代码如下:
饿汉式单例:
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){return singleton;}
}
懒汉式单例:
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){if(singleton==null){singleton = new Singleton();
}
return singleton;
}
}
单例模式还有一种比拟常见的模式:双重锁的模式
public class Singleton{
private static volatile Singleton instance=null;
private Singleton(){//do something}
public static Singleton getInstance(){if(instance==null){synchronized(SingletonClass.class){if(instance==null){instance=new Singleton();
}
}
}
return instance;
}
}
这个模式将同步内容下方到 if 外部,进步了执行的效率,不用每次获取对象时都进行同步,只有第一次才同步,创立了当前就没必要了。这种模式中双重判断加同步的形式,比下面例子中的效率大大晋升,因为如果单层 if 判断,在服务器容许的状况下,假如有一百个线程,消耗的工夫为 100*(同步判断工夫 +if 判断工夫),而如果双重 if 判断,100 的线程能够同时 if 判断,实践耗费的工夫只有一个 if 判断的工夫。
所以如果面对高并发的状况,而且采纳的是懒汉模式,最好的抉择就是双重判断加同步的形式。
单例模式的长处:
1,在内存中只有一个对象,节俭内存空间。2,防止频繁的创立销毁对象,能够进步性能。3,防止对共享资源的多重占用。4,能够全局拜访。
单例模式的毛病:
1,扩大艰难,因为 getInstance 动态函数没有方法生成子类的实例。如果要拓展,只有重写那个类。2,隐式应用引起类构造不清晰。3,可能会导致程序内存泄露的问题。
实用场景:
因为单例模式的以上长处,所以是编程中用的比拟多的一种设计模式。以下为应用单例模式的场景:1,须要频繁实例化而后销毁的对象。2,创建对象时耗时过多或者耗资源过多,但又常常用到的对象。3,资源共享的状况下,防止因为资源操作时导致的性能或损耗等
4,管制资源的状况下,不便资源之间的相互通信。
单例模式注意事项:
1. 只能应用单例类提供的办法失去单例对象,不要应用反射,否则将会实例化一个新对象。2. 不要做断开单例类对象与类中动态援用的危险操作。3. 多线程应用单例应用共享资源时,留神线程平安问题。
对于 Java 中单例模式的一些常见问题:
一. 单例模式的对象长时间不必会被 jvm 垃圾收集器收集吗?
除非人为地断开单例中动态援用到单例对象的联接,否则 jvm 垃圾收集器是不会回收单例对象的。
JVM 卸载类的断定条件如下:
1,该类所有的实例都曾经被回收,也就是 java 堆中不存在该类的任何实例。
2,加载该类的 ClassLoader 曾经被回收。
3,该类对应的 java.lang.Class 对象没有任何中央被援用,无奈在任何中央通过反射拜访该类的办法。
只有三个条件都满足,jvm 才会在垃圾收集的时候卸载类。显然,单例的类不满足条件一,因而单例类也不会被回收。
二. 在一个 jvm 中会呈现多个单例吗?
在分布式系统、多个类加载器、以及序列化的的状况下,会产生多个单例。那么在同一个 jvm 中,会不会产生单例呢?应用单例提供的 getInstance()办法只能失去同一个单例,除非是应用反射形式,将会失去新的单例。
代码如下:
Class c = Class.forName(Singleton.class.getName());
Constructor ct = c.getDeclaredConstructor();
ct.setAccessible(true);
Singleton singleton = (Singleton)ct.newInstance();
这样,每次运行都会产生新的单例对象。所以使用单例模式时,肯定留神不要应用反射产生新的单例对象。
三. 在 getInstance()办法上同步有劣势还是仅同步必要的块更优劣势?
因为锁定仅仅在创立实例时才有意义,而后其余时候实例仅仅是只读拜访的,因而只同步必要的块的性能更优,并且是更好的抉择。毛病:只有在第一次调用的时候,才会呈现生成 2 个对象,才必须要求同步。而一旦 singleton 不为 null,零碎仍旧破费同步锁开销,有点得失相当。
四. 单例类能够被继承吗
依据单例实例结构的机会和形式不同,单例模式还能够分成几种。但对于这种通过私有化构造函数,静态方法提供实例的单例类而言,是不反对继承的。
这种模式的单例实现要求每个具体的单例类本身来保护单例实例和限度多个实例的生成。但能够采纳另外一种实现单例的思路:注销式单例,来使得单例对继承的凋谢。典型列子 Spring IOC 实现原理。
(二) 工厂模式
这种类型的设计模式属于创立型模式,它提供了一种创建对象的最佳形式。
工厂模式次要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到进步灵活性的目标。
工厂模式依据形象水平的不同分为三种:
1. 简略工厂模式(也叫动态工厂模式)
2. 工厂办法模式(也叫多形性工厂)
3. 形象工厂模式(也叫工具箱)
简略工厂模式:
本质是由一个工厂类依据传入的参数,动静决定应该创立哪一个产品类(这些产品类继承自一个父类或接口)的实例。简略工厂模式的创立指标,所有创立的对象都是充当这个角色的某个具体类的实例。
工厂办法模式:
工厂办法是粒度很小的设计模式,因为模式的体现只是一个形象的办法。提前定义用于创建对象的接口,让子类决定实例化具体的某一个类,即在工厂和产品两头减少接口,工厂不再负责产品的创立,由接口针对不同条件返回具体的类实例,由具体类实例去实现。
形象工厂模式:
当有多个形象角色时应用的一种工厂模式。形象工厂模式能够向客户端提供一个接口,使客户端在不用指定产品的具体的状况下,创立多个产品对象。它有多个形象产品类,每个形象产品类能够派生出多个具体产品类,一个形象工厂类,能够派生出多个具体工厂类,每个具体工厂类能够创立多个具体产品类的实例。
工厂办法模式应该在理论中用的较多,咱们以工厂办法模式举例
形象的产品类:定义 car 交通工具类
public interface Car {void gotowork();
}
定义理论的产品类,总共定义两个,bike 和 bus 别离示意不同的交通工具类
public class Bike implements Car {
@Override
public void goToWork() {System.out.println("骑自行车去下班!");
}
}
public class Bus implements Car {
@Override
public void goToWork() {System.out.println("坐公交车去下班!");
}
}
定义形象的工厂接口
public interface ICarFactory {Car getCar();
}
具体的工厂子类,别离为每个具体的产品类创立不同的工厂子类
public class BikeFactory implements ICarFactory {
@Override
public Car getCar() {return new Bike();
}
}
public class BusFactory implements ICarFactory {
@Override
public Car getCar() {return new Bus();
}
}
简略的测试类,来验证不同的工厂可能产生不同的产品对象
public class TestFactory {
@Test
public void test() {
ICarFactory factory = null;
// bike
factory = new BikeFactory();
Car bike = factory.getCar();
bike.goToW ork(); // 骑自行车去下班!// bus
factory = new BusFactory();
Car bus = factory.getCar();
bus. goToWork(); // 坐公交车去下班!}
}
工厂模式的长处:
1、一个调用者想创立一个对象,只有晓得其名称就能够了,升高了耦合度。2、扩展性高,如果想减少一个产品,只有扩大一个工厂类就能够。使得代码构造更加清晰。3、屏蔽产品的具体实现,调用者只关怀产品的接口。
工厂模式的毛病:
每次减少一个产品时,都须要减少一个具体类和对象实现工厂,使得零碎中类的个数成倍增加,在肯定水平上减少了零碎的复杂度,同时也减少了零碎具体类的依赖。所以对于简略对象来说,应用工厂模式反而减少了复杂度。
工厂模式的实用场景:
1,一个对象领有很多子类。2,创立某个对象时须要进行许多额定的操作。3,零碎前期须要常常扩大,它把对象实例化的工作交由实现类实现,扩展性好。
对于 Java 中的工厂模式的一些常见问题:
1. 利用父类的向下转型(应用父类类型的援用指向子类的对象)是能够达到相似于工厂模式的成果的,那为什么还要用工厂模式呢?把指向子类对象的父类援用赋给子类援用叫做向下转型,如:Class Student extends Person
Person s = new Student();
Student a = (Student)s;
应用向下转型在客户端实例化子类的时候,重大依赖具体的子类的名字。当咱们须要更改子类的构造方法的时候,比方减少一个参数,或者更改了子类的类名,所有的 new 进去的子类都须要跟着更改。
但如果咱们应用工厂模式,咱们仅仅须要在工厂中批改一下 new 的代码,其余我的项目中用到此实例的都会跟着改,而不须要咱们手动去操作。
总结:
无论是简略工厂模式、工厂办法模式还是形象工厂模式,它们实质上都是将不变的局部提取进去,将可变的局部留作接口,以达到最大水平上的复用。
(三) 模板办法模式
定义一个操作中算法的框架,而将一些步骤提早到子类中,使得子类能够不扭转算法的构造即可重定义该算法中的某些特定步骤。
类型:行为类模式
类图:
事实上,模版办法是编程中一个常常用到的模式。先来看一个例子,某日,程序员 A 拿到一个工作:给定一个整数数组,把数组中的数由小到大排序,而后把排序之后的后果打印进去。通过剖析之后,这个工作大体上可分为两局部,排序和打印,打印性能好实现,排序就有点麻烦了。然而 A 有方法,先把打印性能实现,排序功能另找人做。
abstract class AbstractSort {
/**
* 将数组 array 由小到大排序
* @param array
*/
protected abstract void sort(int[] array);
public void showSortResult(int[] array){this.sort(array);
System.out.print("排序后果:");
for (int i = 0; i < array.length; i++){System.out.printf("%3s", array[i]);
}
}
}
写完后,A 找到共事 B 说:有个工作,次要逻辑我曾经写好了,你把剩下的逻辑实现一下吧。于是把 AbstractSort 类给 B,让 B 写实现。B 拿过去一看,太简略了,10 分钟搞定,代码如下:
class ConcreteSort extends AbstractSort {
@Override
protected void sort(int[] array){for(int i=0; i<array.length-1; i++){selectSort(array, i);
}
}
private void selectSort(int[] array, int index) {
int MinValue = 32767; // 最小值变量
int indexMin = 0; // 最小值索引变量
int Temp; // 暂存变量
for (int i = index; i < array.length; i++) {if (array[i] < MinValue){ // 找到最小值
MinValue = array[i]; // 贮存最小值
indexMin = i;
}
}
Temp = array[index]; // 替换两数值
array[index] = array[indexMin];
array[indexMin] = Temp;
}
}
写好后交给 A,A 拿来一运行:
public class Client {public static int[] a = {10, 32, 1, 9, 5, 7, 12, 0, 4, 3};
// 预设数据数组
public static void main(String[] args){AbstractSort s = new ConcreteSort();
s.showSortResult(a);
}
}
运行后果:
排序后果:0 1 3 4 5 7 9 10 12 32
运行失常。行了,工作实现。这就是模版办法模式。
模版办法模式的构造
模版办法模式由一个抽象类和一个(或一组)实现类通过继承构造组成
抽象类中的办法分为三种:
1. 形象办法:父类中只申明但不加以实现,而是定义好标准,而后由它的子类去实现。
2. 模版办法:由抽象类申明并加以实现。一般来说,模版办法调用形象办法来实现次要的逻辑性能,并且,模版办法大多会定义为 final 类型,指明次要的逻辑性能在子类中不能被重写。
3. 钩子办法:由抽象类申明并加以实现。然而子类能够去扩大,子类能够通过扩大钩子办法来影响模版办法的逻辑。
实现类用来实现细节。抽象类中的模版办法正是通过实现类扩大的办法来实现业务逻辑。只有实现类中的扩大办法通过了单元测试,在模版办法正确的前提下,整体性能个别不会呈现大的谬误。
模版办法的长处及实用场景
容易扩大。一般来说,抽象类中的模版办法是不易反生扭转的局部,而形象办法是容易反生变动的局部,因而通过减少实现类个别能够很容易实现性能的扩大,合乎开闭准则。便于保护。对于模版办法模式来说,正是因为他们的次要逻辑雷同,才应用了模版办法,如果不应用模版办法,任由这些雷同的代码散乱的散布在不同的类中,保护起来是十分不不便的。比拟灵便。因为有钩子办法,因而,子类的实现也能够影响父类中主逻辑的运行。然而,在灵便的同时,因为子类影响到了父类,违反了里氏替换准则,也会给程序带来危险。这就对抽象类的设计有了更高的要求。在多个子类领有雷同的办法,并且这些办法逻辑雷同时,能够思考应用模版办法模式。在程序的主框架雷同,细节不同的场合下,也比拟适宜应用这种模式。
(四) 策略模式
定义一组算法,将每个算法都封装起来,并且使他们之间能够调换。
类型:行为类模式
类图:
策略模式是对算法的封装,把一系列的算法别离封装到对应的类中,并且这些类实现雷同的接口,相互之间能够替换。在后面说过的模版办法模式中,也是关注对算法的封装。对照类图能够看到,策略模式与模版办法模式的区别仅仅是多了一个独自的封装类 Context,它与模版办法模式的区别在于:在模版办法模式中,调用算法的主体在形象的父类中,而在策略模式中,调用算法的主体则是封装到了封装类 Context 中,形象策略 Strategy 个别是一个接口,目标只是为了定义标准,外面个别不蕴含逻辑。其实,这只是通用实现,而在理论编程中,因为各个具体策略实现类之间不免存在一些雷同的逻辑,为了防止反复的代码,咱们经常应用抽象类来负责 Strategy 的角色,在外面封装公共的代码,因而,在很多利用的场景中,在策略模式中个别会看到模版办法模式的影子。
策略模式的构造
1. 封装类:也叫上下文,对策略进行二次封装,目标是防止高层模块对策略的间接调用。
2. 形象策略:通常状况下为一个接口,当各个实现类中存在着反复的逻辑时,则应用象类来封装这部分公共的代码,此时,策略模式看上去更像是模版办法模式。
3. 具体策略:具体策略角色通常由一组封装了算法的类来负责,这些类之间能够依据须要自在替换。
策略模式代码实现
interface IStrategy {public void doSomething();
}
class ConcreteStrategy1 implements IStrategy {public void doSomething() {System.out.println("具体策略 1");
}
}
class ConcreteStrategy2 implements IStrategy {public void doSomething() {System.out.println("具体策略 2");
}
}
class Context {
private IStrategy strategy;
public Context(IStrategy strategy){this.strategy = strategy;}
public void execute(){strategy.doSomething();
}
}
public class Client {public static void main(String[] args){
Context context;
System.out.println("----- 执行策略 1 -----");
context = new Context(new ConcreteStrategy1());
context.execute();
System.out.println("----- 执行策略 2 -----");
context = new Context(new ConcreteStrategy2());
context.execute();}
}
策略模式的优缺点
策略模式的次要长处有:
(一) 策略类之间能够自在切换,因为策略类实现自同一个形象,所以他们之间能够自在切换。
(二) 易于扩大,减少一个新的策略对策略模式来说非常容易,基本上能够在不扭转原有代码的根底上进行扩大。
(三) 防止应用多重条件,如果不应用策略模式,对于所有的算法,必须应用条件语句进行连贯,通过条件判断来决定应用哪一种算法,在上一篇文章中咱们曾经提到,应用多重条件判断是十分不容易保护的。
策略模式的毛病次要有两个:
(一) 保护各个策略类会给开发带来额定开销,可能大家在这方面都有教训:一般来说,策略类的数量超过 5 个,就比拟令人头疼了。
(二) 必须对客户端(调用者)裸露所有的策略类,因为应用哪种策略是由客户端来决定的,因而,客户端应该晓得有什么策略,并且理解各种策略之间的区别,否则,结果很重大。例如,有一个排序算法的策略模式,提供了疾速排序、冒泡排序、抉择排序这三种算法,客户端在应用这些算法之前,是不是先要明确这三种算法的实用状况?再比方,客户端要应用一个容器,有链表实现的,也有数组实现的,客户端是不是也要明确链表和数组有什么区别?
实用场景:
做面向对象设计的,对策略模式肯定很相熟,因为它本质上就是面向对象中的继承和多态,在看完策略模式的通用代码后,我想,即便之前素来没有据说过策略模式,在开发过程中也肯定应用过它吧?至多在在以下两种状况下,大家能够思考应用策略模式:
(一) 几个类的次要逻辑雷同,只在局部逻辑的算法和行为上稍有区别的状况。
(二) 有几种类似的行为,或者说算法,客户端须要动静地决定应用哪一种,那么能够应用策略模式,将这些算法封装起来供客户端调用。
策略模式是一种简略罕用的模式,咱们在进行开发的时候,会常常有意无意地应用它,一般来说,策略模式不会独自应用,跟模版办法模式、工厂模式等混合应用的状况比拟多。