設計模式學習總結(2)單例模式、建造者模式、原型模式
單例模式(Singleton Pattern)
這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。
單例模式有以下三點注意:
- 1、單例類只能有一個例項。
- 2、單例類必須自己建立自己的唯一例項。
- 3、單例類必須給所有其他物件提供這一例項。
單例的好處:
- 某些類建立比較頻繁,對於一些大型的物件,這是一筆很大的系統開銷。
- 省去了new操作符,降低了系統記憶體的使用頻率,減輕GC壓力。
- 有些類如交易所的核心交易引擎,控制著交易流程,如果該類可以建立多個的話,系統完全亂了。(比如一個軍隊出現了多個司令員同時指揮,肯定會亂成一團),所以只有使用單例模式,才能保證核心交易伺服器獨立控制整個流程。
缺點:
沒有介面,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來例項化。
注意:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多執行緒同時進入造成 instance 被多次例項化。
單例模式的實現方式一共有6種,一般情況下,不建議使用第 1 種和第 2 種懶漢方式,建議使用第 3 種餓漢方式。只有在要明確實現 lazy loading 效果時,才會使用第 5 種登記方式(建議使用這個方法)。如果涉及到反序列化建立物件時,可以嘗試使用第 6 種列舉方式。如果有其他特殊的需求,可以考慮使用第 4 種雙檢鎖方式。
1、懶漢式,執行緒不安全
是否 Lazy 初始化:是
是否多執行緒安全:否
總結:最大的問題就是不支援多執行緒,
/** * @ClassName: Singleton1 * @Description: 懶漢式,執行緒不安全 * @Author: xinyuan * @CreateDate: 2018/9/24 11:18 */ public class Singleton1 { private static Singleton1 instance; private Singleton1(){} public static Singleton1 getInstance(){ if(instance == null){ instance = new Singleton1(); } return instance; } public void showMessage(){ System.out.println("hello singleton1"); } }
/**
* @ClassName: SingletonPatternDemo
* @Description: 測試demo
* @Author: xinyuan
* @CreateDate: 2018/9/24 11:19
*/
public class SingletonPatternDemo {
public static void main(String[] args){
Singleton1 singleton1 = Singleton1.getInstance();
singleton1.showMessage();
}
}
2、懶漢式,執行緒安全
是否 Lazy 初始化:是
是否多執行緒安全:是
總結:能進行多執行緒,但是加鎖會導致效率很低,優點就是避免了記憶體的浪費。這個還是有一個問題的,就是看到別的大神指出,如果A執行緒進入getInstance函式建立instance例項,不過並沒有進行賦值操作,但是B執行緒這時也要使用這個類建立的唯一例項,但是他發現instance已經建立,但是訪問卻什麼也沒有,因而出現問題。
/**
* @ClassName: Singleton2
* @Description: 懶漢式,執行緒安全
* @Author: xinyuan
* @CreateDate: 2018/9/24 11:28
*/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2(){}
public static synchronized Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
public void showMessage(){
System.out.println("hello singleton2");
}
}
3、餓漢式
是否 Lazy 初始化:否
是否多執行緒安全:是
總結:它基於 classloader 機制避免了多執行緒的同步問題,但是缺點就是造成了記憶體的浪費,沒有達到lazy loading的效果。我認為相當於把instance的宣告和獲取分開了。
/**
* @ClassName: Singleton3
* @Description: 餓漢式
* @Author: xinyuan
* @CreateDate: 2018/9/24 11:37
*/
public class Singleton3 {
private static Singleton3 instance = new Singleton3();
private Singleton3(){}
public static Singleton3 getInstance(){
return instance;
}
public void showMessage(){
System.out.println("hello singleton3");
}
}
4、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
是否 Lazy 初始化:是
是否多執行緒安全:是
總結:這種方式採用雙鎖機制,安全且在多執行緒情況下能保持高效能。(雖然程式碼也模仿的寫出來了,但是感覺還是有點不明白啊)
/**
* @ClassName: Singleton4
* @Description: java類作用描述
* @Author: xinyuan
* @CreateDate: 2018/9/24 11:47
*/
public class Singleton4 {
private volatile static Singleton4 instance;
private Singleton4(){}
public static Singleton4 getInstance(){
if(instance == null){
synchronized (Singleton4.class){
if(instance == null){
instance = new Singleton4();
}
}
}
return instance;
}
public void showMessage(){
System.out.println("hello singleton4");
}
}
5、登記式/靜態內部類
是否 Lazy 初始化:是
是否多執行緒安全:是
總結:也是利用了classloader機制保證初始化instance只有一個執行緒,採用了類裡面再單獨使用一個類去初始化的方法,可以有效地去避免記憶體的浪費,還可以保證效率,是一個常用的辦法
/**
* @ClassName: Singleton5
* @Description: 登記式/靜態內部類
* @Author: xinyuan
* @CreateDate: 2018/9/24 11:54
*/
public class Singleton5 {
private static class SingletonHolder{
private static final Singleton5 instance = new Singleton5();
}
private Singleton5(){}
public static Singleton5 getInstance(){
return SingletonHolder.instance;
}
public void showMessage(){
System.out.println("hello singleton5");
}
}
6、列舉
是否 Lazy 初始化:否
是否多執行緒安全:是
總結:這種實現方式還沒有被廣泛採用,但這是實現單例模式的最佳方法。它更簡潔,自動支援序列化機制,絕對防止多次例項化。
這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多執行緒同步問題,而且還自動支援序列化機制,防止反序列化重新建立新的物件,絕對防止多次例項化。不過,由於 JDK1.5 之後才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。
不能通過 reflection attack 來呼叫私有構造方法。(這個方法還需要再研究啊。。。)
/**
* @ClassName: Singleton6
* @Description: 列舉
* @Author: xinyuan
* @CreateDate: 2018/9/24 12:06
*/
public enum Singleton6 {
INSTANCE;
public void showMessage(){
System.out.println("hello singleton6");
}
}
//方法6:列舉
Singleton6 singleton6 = Singleton6.INSTANCE;
singleton6.showMessage();
建造者模式(Builder Pattern)
一個 Builder 類會一步一步構造最終的物件。該 Builder 類是獨立於其他物件的。
主要解決:主要解決在軟體系統中,有時候面臨著"一個複雜物件"的建立工作,其通常由各個部分的子物件用一定的演算法構成;由於需求的變化,這個複雜物件的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的演算法卻相對穩定。
優點: 1、建造者獨立,易擴充套件。 2、便於控制細節風險。
缺點: 1、產品必須有共同點,範圍有限制。 2、如內部變化複雜,會有很多的建造類。
/**
* @ClassName: Packing
* @Description: 包裝類介面
* @Author: xinyuan
* @CreateDate: 2018/9/24 12:30
*/
public interface Packing {
public String pack();
}
/**
* @ClassName: Bottle
* @Description: 包裝類介面實體類
* @Author: xinyuan
* @CreateDate: 2018/9/24 12:32
*/
public class Bottle implements Packing{
@Override
public String pack() {
return "bottle";
}
}
/**
* @ClassName: Wrapper
* @Description: 紙質包裝實體類
* @Author: xinyuan
* @CreateDate: 2018/9/24 12:31
*/
public class Wrapper implements Packing{
@Override
public String pack(){
return "wrapper";
}
}
/**
* @ClassName: Item
* @Description: 食物條目介面
* @Author: xinyuan
* @CreateDate: 2018/9/24 12:29
*/
public interface Item {
public String name();
public Packing packing();
public float price();
}
/**
* @ClassName: Burger
* @Description: java類作用描述
* @Author: xinyuan
* @CreateDate: 2018/9/24 12:33
*/
public abstract class Burger implements Item{
@Override
public Packing packing() {
return new Wrapper();
}
// @Override
// public abstract float price();
}
/**
* @ClassName: ColdDrink
* @Description: java類作用描述
* @Author: xinyuan
* @CreateDate: 2018/9/24 13:11
*/
public abstract class ColdDrink implements Item{
@Override
public Packing packing() {
return new Bottle();
}
}
/**
* @ClassName: VerBurger
* @Description: java類作用描述
* @Author: xinyuan
* @CreateDate: 2018/9/24 13:12
*/
public class VerBurger extends Burger{
@Override
public float price() {
return 25.0f;
}
@Override
public String name() {
return "VerBurger";
}
}
/**
* @ClassName: ChickenBurger
* @Description: java類作用描述
* @Author: xinyuan
* @CreateDate: 2018/9/24 13:14
*/
public class ChickenBurger extends Burger{
@Override
public float price() {
return 50.5f;
}
@Override
public String name() {
return "chickenbuger";
}
}
/**
* @ClassName: Coke
* @Description: java類作用描述
* @Author: xinyuan
* @CreateDate: 2018/9/24 13:15
*/
public class Coke extends ColdDrink{
@Override
public float price() {
return 10.0f;
}
@Override
public String name() {
return "coke";
}
}
/**
* @ClassName: Pepsi
* @Description: java類作用描述
* @Author: xinyuan
* @CreateDate: 2018/9/24 13:16
*/
public class Pepsi extends ColdDrink{
@Override
public float price() {
return 9.0f;
}
@Override
public String name() {
return "pepsi";
}
}
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: Meal
* @Description: 建立一個 Meal 類,帶有上面定義的 Item 物件
* @Author: xinyuan
* @CreateDate: 2018/9/24 13:21
*/
public class Meal {
public List<Item> items = new ArrayList<Item>();
public void addItem(Item item){
items.add(item);
}
public float getCost(){
float cost = 0.0f;
for(Item item : items){
cost += item.price();
}
return cost;
}
public void showItems(){
for(Item item : items){
System.out.println("Item:"+item.name());
System.out.println("Packing:"+item.packing().pack());
System.out.println("price:"+item.price());
}
}
}
/**
* @ClassName: MealBuilder
* @Description: 建立一個 MealBuilder 類,實際的 builder 類負責建立 Meal 物件。
* @Author: xinyuan
* @CreateDate: 2018/9/24 13:27
*/
public class MealBuilder {
public Meal prepareVegMeal(){
Meal meal = new Meal();
meal.addItem(new VerBurger());
meal.addItem(new Coke());
return meal;
}
public Meal perpareChickenMeal(){
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}
/**
* @ClassName: BuilderPatternDemo
* @Description: BuiderPatternDemo 使用 MealBuider 來演示建造者模式(Builder Pattern)。
* @Author: xinyuan
* @CreateDate: 2018/9/24 13:31
*/
public class BuilderPatternDemo {
public static void main(String[] args){
MealBuilder mealBuilder = new MealBuilder();
Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("veg meal");
vegMeal.showItems();
System.out.println("total cost:" + vegMeal.getCost());
Meal chickMeal = mealBuilder.perpareChickenMeal();
System.out.println("chick meal");
chickMeal.showItems();
System.out.println("total cost:" + chickMeal.getCost());
}
}
原型模式(Prototype Pattern)
這種模式是實現了一個原型介面,該介面用於建立當前物件的克隆。當直接建立物件的代價比較大時,則採用這種模式。例如,一個物件需要在一個高代價的資料庫操作之後被建立。我們可以快取該物件,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫呼叫。
注意事項:與通過對一個類進行例項化來構造新物件不同的是,原型模式是通過拷貝一個現有物件生成新物件的。淺拷貝實現 Cloneable,重寫,深拷貝是通過實現 Serializable 讀取二進位制流。
/**
* @ClassName: Shape
* @Description: 建立一個實現了 Clonable 介面的抽象類。
* @Author: xinyuan
* @CreateDate: 2018/9/24 14:16
*/
public abstract class Shape implements Cloneable{
private String id;
protected String type;
abstract void draw();
public String getType() {
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object clone(){
Object clone = null;
try{
clone = super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return clone;
}
}
/**
* @ClassName: Square
* @Description: java類作用描述
* @Author: xinyuan
* @CreateDate: 2018/9/24 14:23
*/
public class Square extends Shape{
public Square(){
type = "square";
}
public void draw(){
System.out.println("draw square");
}
}
/**
* @ClassName: Rectangle
* @Description: 拓展抽象類的實體類
* @Author: xinyuan
* @CreateDate: 2018/9/24 14:21
*/
public class Rectangle extends Shape{
public Rectangle(){
type = "Rectangle";
}
public void draw(){
System.out.println("draw rectangle");
}
}
import java.util.Hashtable;
/**
* @ClassName: ShapeCache
* @Description: 建立一個類,從資料庫獲取實體類,並把它們儲存在一個 Hashtable 中。
* @Author: xinyuan
* @CreateDate: 2018/9/24 14:55
*/
public class ShapeCache {
private static Hashtable<String,Shape> shapeMap = new Hashtable<String,Shape>();
public static Shape getShape(String shapeId){
Shape cachedShape = shapeMap.get(shapeId);
return (Shape)cachedShape.clone();
}
public static void loadCache(){
Square square = new Square();
square.setId("1");
shapeMap.put(square.getId(),square);
Rectangle rectangle = new Rectangle();
rectangle.setId("2");
shapeMap.put(rectangle.getId(),rectangle);
}
}
/**
* @ClassName: PrototypePatternDemo
* @Description: PrototypePatternDemo 使用 ShapeCache 類來獲取儲存在 Hashtable 中的形狀的克隆。
* @Author: xinyuan
* @CreateDate: 2018/9/24 15:03
*/
public class PrototypePatternDemo {
public static void main(String[] args){
ShapeCache.loadCache();
Shape clonedShape = (Shape) ShapeCache.getShape("1");
System.out.println("Shape: " + clonedShape.getType());
Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
System.out.println("Shape: " + clonedShape2.getType());
}
}