常用設計模式總結
參考:
https://blog.csdn.net/qq_14827935/article/details/78618652
https://www.cnblogs.com/xiaofengwang/p/11255678.html
https://www.cnblogs.com/yueguanguanyun/p/9584501.html
java常用設計模式總結
掌握常用的幾種(最起碼單例模式、工廠模式),瞭解其他的設計模式即可,做到手裡有糧,心裡不慌。首先,掌握每種模式的定義及使用場景。其次,掌握一個形象的例子,簡單的過一遍程式碼。
學習設計模式的真正目的:程式設計時,有意識地面向介面程式設計,多用封裝、繼承、組合、多型等OOP思想,而不僅僅是死記幾類設計模式。
常用的設計模式分類:
建立型(建立一個物件):單例模式、工廠模式、抽象工廠模式
結構型(將幾個物件組織成一個結構):橋接模式、外觀模式、代理模式
行為型(多個物件間的通訊):觀察者模式、策略模式
其中,工廠模式、橋接模式、策略模式有點像,放在一起理解(幾個物件具有共同特徵,因此繼承共同的介面,然後通過工廠、橋去訪問)。另外,工廠模式和外觀模式(幾個物件間有先後關係,是序列的,而非工廠模式中的並行,因此幾個物件組合成一個外觀類,通過這個外觀類來訪問)區別很明顯,也因此放在一起理解。
參考引用:
http://www.runoob.com/design-pattern/proxy-pattern.html
https://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
https://www.cnblogs.com/chinajava/p/5880870.html
設計模式定義
被反覆使用的,程式碼設計經驗的總結。
設計模式的原則
總結起來,就是多用介面/抽象類,從而增加程式碼的可擴充套件性(減少修改程式碼)。降低模組間的依賴和聯絡。
體現了OOP的模組化、可擴充套件性等特徵。
工廠模式
定義與使用場合:現在需要建立幾個物件,且這幾個物件有共同特徵,則不需要具體建立各個物件,而是建立物件工廠類即可。
一般常用靜態工廠模式。
例子:傳送郵件和簡訊(共同特徵:傳送的訊息)
public interface Sender {
public void Send();
}
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
抽象工廠模式
工廠方法模式有一個問題就是,類的建立依賴工廠類,也就是說,如果想要拓展程式,必須對工廠類進行修改,這違背了閉包原則。
定義與使用場景:同上。
例子:同上。
public interface Provider {
public Sender produce();
}
public class SendMailFactory implements Provider {
@Override
public Sender produce(){
return new MailSender();
}
}
public class SendSmsFactory implements Provider{
@Override
public Sender produce() {
return new SmsSender();
}
}
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
總結:如果要新增傳送微信,則只需做一個實現類,實現Sender介面,同時做一個工廠類,實現Provider介面,就OK了,無需去改動現成的程式碼。這樣做,拓展性較好!
所有工廠模式中,抽象工廠模式最先進。
策略模式及與工廠模式的區別
定義與使用場合:一個系統需要動態地在幾種類似的演算法中選擇一種。
與工廠模式異同:例項化一個物件的位置不同。對工廠模式而言,例項化物件是放在了工廠類裡面。而策略模式例項化物件的操作在呼叫的地方。本質都是繼承與多型。
例子:現有 加/減/乘 幾種演算法,輸入引數返回值都一樣(可以理解成類似的演算法)。現在需要在呼叫時動態配置演算法策略,實現對不同演算法的呼叫。
public interface Strategy {
public int doOperation(int num1, int num2);
}
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}
public class StrategyPatternDemo {
public static void main(String[] args) {
//例項化物件的位置在呼叫處
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
單例模式
定義及使用場合:只有一個物件被建立。
例子:
建議採用 餓漢式 建立方法。執行緒安全,容易實現。初始化慢一點。
public class SingleObject {
//建立 SingleObject 的一個物件
private static SingleObject instance = new SingleObject();
//讓建構函式為 private,這樣該類就不會被例項化
private SingleObject(){}
//獲取唯一可用的物件
public static SingleObject getInstance(){
return instance;
}
}
觀察者模式
定義與使用場景:一個物件(subject)被其他多個物件(observer)所依賴。則當一個物件變化時,發出通知,其它依賴該物件的物件都會收到通知,並且隨著變化。
比如 聲音報警器和閃光燈報警器分別訂閱熱水器溫度,熱水器溫度過高時,發出通知,兩個報警器分別發聲、閃光以實現報警。
又比如很多人訂閱微信公眾號,該公眾號有更新文章時,自動通知每個訂閱的使用者。
**實現:**1,多個觀察者要訂閱這個物件 2,這個物件要發出通知
例子:
public interface Observer {
public void update();
}
public class Observer1 implements Observer {
@Override
public void update() {
System.out.println("observer1 has received!");
}
}
public class Observer2 implements Observer {
@Override
public void update() {
System.out.println("observer2 has received!");
}
}
public interface Subject {
/*增加觀察者*/
public void add(Observer observer);
/*刪除觀察者*/
public void del(Observer observer);
/*通知所有的觀察者*/
public void notifyObservers();
/*自身的操作*/
public void operation();
}
public abstract class AbstractSubject implements Subject {
private Vector<Observer> vector = new Vector<Observer>();
@Override
public void add(Observer observer) {
vector.add(observer);
}
@Override
public void del(Observer observer) {
vector.remove(observer);
}
@Override
public void notifyObservers() {
Enumeration<Observer> enumo = vector.elements();
while(enumo.hasMoreElements()){
enumo.nextElement().update();
}
}
public class MySubject extends AbstractSubject {
@Override
public void operation() {
System.out.println("update self!");
notifyObservers();
}
}
public class ObserverTest {
public static void main(String[] args) {
Subject sub = new MySubject();
sub.add(new Observer1()); //訂閱這個物件
sub.add(new Observer2());
sub.operation(); //發出改變的一個通知
}
}
代理模式
定義與使用場景:一個代理類代表一個真實類的功能,通過訪問代理類來實現對真實類的訪問。
比如買火車票這件小事:黃牛相當於是火車站的代理,我們可以通過黃牛買票,但只能去火車站進行改簽和退票。
又比如需要對原有的方法進行修改,就是採用一個代理類呼叫原有的方法,以避免修改原有程式碼。
例子:
一個真實物件realSubject提供一個代理物件proxy。通過proxy可以呼叫realSubject的部分功能*,並新增一些額外的業務處理*,同時可以遮蔽realSubject中未開放的介面。
1、RealSubject 是委託類,Proxy 是代理類;
2、Subject 是委託類和代理類的介面;
3、request() 是委託類和代理類的共同方法;
interface Subject {
void request();
}
class RealSubject implements Subject {
public void request(){
System.out.println("RealSubject");
}
}
class Proxy implements Subject {
private Subject subject;
public Proxy(Subject subject){
this.subject = subject;
}
public void request(){
System.out.println("begin");
subject.request();
System.out.println("end");
}
}
public class ProxyTest {
public static void main(String args[]) {
RealSubject subject = new RealSubject();
Proxy p = new Proxy(subject);
p.request();
}
}
橋接模式及與策略模式的區別
定義與使用場景:訪問多種資料庫驅動(多個具有共同特徵的資料庫驅動),不是直接訪問,而是通過DriverManager橋來訪問。
例子:不再具體實現了。
與策略模式的區別:(個人覺得較複雜,瞭解即可。本質都是面向介面程式設計,體現繼承與多型)
策略模式:我要畫圓,要實心圓,我可以用solidPen來配置,畫虛線圓可以用dashedPen來配置。這是strategy模式。
橋接模式:同樣是畫圓,我是在windows下來畫實心圓,就用windowPen+solidPen來配置,在unix下畫實心圓就用unixPen+solidPen來配置。如果要再windows下畫虛線圓,就用windowsPen+dashedPen來配置,要在unix下畫虛線圓,就用unixPen+dashedPen來配置。
所以相對策略模式,橋接模式要表達的內容要更多,結構也更加複雜。
外觀模式
定義與使用場景:見例子。又比如,去醫院看病,可能要去掛號、門診、劃價、取藥,讓患者或患者家屬覺得很複雜,如果有提供接待人員,只讓接待人員來處理,就很方便。
例子:計算機啟動,需要先啟動CPU,再啟動memory,最後啟動disk。這三個類之間具有先後關係(依賴關係)。
與工廠模式的區別:工程模式多個類具有共同特徵(繼承一個共同的介面),是並列的。而外觀模式多個類是有先後關係,是序列的,用組合。
貼部分程式碼:
public class Computer {
//是組合,而非繼承。這是與工程模式的顯著區別。
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup(){
System.out.println("start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finished!");
}
public void shutdown(){
System.out.println("begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("computer closed!");
}
}
public class User {
public static void main(String[] args) {
Computer computer = new Computer();
//將計算機的啟動過程封裝成一個類
computer.startup();
computer.shutdown();
}
}
生產者-消費者模式
定義與使用場景:生產者把資料放入緩衝區,而消費者從緩衝區取出資料。
例子:緩衝區一般為佇列(FIFO),但在生產消費較為頻繁時,佇列push,pop記憶體消耗較大,此時可以考慮環形緩衝區(以陣列、連結串列方式實現)。
通過互斥鎖防止緩衝區同時讀寫。通過訊號量控制緩衝區大小(滿的時候不允許寫,空的時候不允許讀)
Java常見的幾種設計模式
單例模式
簡單點說,就是一個應用程式中,某個類的例項物件只有一個,你沒有辦法去new,因為構造器是被private修飾的,一般通過getInstance()的方法來獲取它們的例項。
getInstance()的返回值是一個物件的引用,並不是一個新的例項,所以不要錯誤的理解成多個物件。單例模式實現起來也很容易,直接看demo吧
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
按照我的習慣,我恨不得寫滿註釋,怕你們看不懂,但是這個程式碼實在太簡單了,所以我沒寫任何註釋,如果這幾行程式碼你都看不明白的話,那你可以洗洗睡了,等你睡醒了再來看我的部落格說不定能看懂。
上面的是最基本的寫法,也叫懶漢寫法(執行緒不安全)下面我再公佈幾種單例模式的寫法:
懶漢式寫法(執行緒安全)
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
餓漢式寫法
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
靜態內部類
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
列舉
public enum Singleton { INSTANCE; public void whateverMethod() { } }
這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件,可謂是很堅強的壁壘啊,不過,個人認為由於1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏。
雙重校驗鎖
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
總結:我個人比較喜歡靜態內部類寫法和餓漢式寫法,其實這兩種寫法能夠應付絕大多數情況了。其他寫法也可以選擇,主要還是看業務需求吧。
觀察者模式
物件間一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。
觀察者模式UML圖
看不懂圖的人端著小板凳到這裡來,給你舉個栗子:假設有三個人,小美(女,22),小王和小李。小美很漂亮,小王和小李是兩個程式猿,時刻關注著小美的一舉一動。有一天,小美說了一句:“誰來陪我打遊戲啊。”這句話被小王和小李聽到了,結果樂壞了,蹭蹭蹭,沒一會兒,小王就衝到小美家門口了,在這裡,小美是被觀察者,小王和小李是觀察者,被觀察者發出一條資訊,然後觀察者們進行相應的處理,看程式碼:
public interface Person { //小王和小李通過這個介面可以接收到小美髮過來的訊息 void getMessage(String s); }
l
這個介面相當於小王和小李的電話號碼,小美髮送通知的時候就會撥打getMessage這個電話,撥打電話就是呼叫介面,看不懂沒關係,先往下看
public class LaoWang implements Person { private String name = "小王"; public LaoWang() { } @Override public void getMessage(String s) { System.out.println(name + "接到了小美打過來的電話,電話內容是:" + s); } } public class LaoLi implements Person { private String name = "小李"; public LaoLi() { } @Override public void getMessage(String s) { System.out.println(name + "接到了小美打過來的電話,電話內容是:->" + s); } }
程式碼很簡單,我們再看看小美的程式碼:
public class XiaoMei { List<Person> list = new ArrayList<Person>(); public XiaoMei(){ } public void addPerson(Person person){ list.add(person); } //遍歷list,把自己的通知傳送給所有暗戀自己的人 public void notifyPerson() { for(Person person:list){ person.getMessage("你們過來吧,誰先過來誰就能陪我一起玩兒遊戲!"); } } }
我們寫一個測試類來看一下結果對不對
public class Test { public static void main(String[] args) { XiaoMei xiao_mei = new XiaoMei(); LaoWang lao_wang = new LaoWang(); LaoLi lao_li = new LaoLi(); //小王和小李在小美那裡都註冊了一下 xiao_mei.addPerson(lao_wang); xiao_mei.addPerson(lao_li); //小美向小王和小李傳送通知 xiao_mei.notifyPerson(); } }
完美~
裝飾者模式
對已有的業務邏輯進一步的封裝,使其增加額外的功能,如Java中的IO流就使用了裝飾者模式,使用者在使用的時候,可以任意組裝,達到自己想要的效果。 舉個栗子,我想吃三明治,首先我需要一根大大的香腸,我喜歡吃奶油,在香腸上面加一點奶油,再放一點蔬菜,最後再用兩片面包夾一下,很豐盛的一頓午飯,營養又健康。(ps:不知道上海哪裡有賣好吃的三明治的,求推薦~)那我們應該怎麼來寫程式碼呢? 首先,我們需要寫一個Food類,讓其他所有食物都來繼承這個類,看程式碼:
public class Food { private String food_name; public Food() { } public Food(String food_name) { this.food_name = food_name; } public String make() { return food_name; }; }
程式碼很簡單,我就不解釋了,然後我們寫幾個子類繼承它:
//麵包類 public class Bread extends Food { private Food basic_food; public Bread(Food basic_food) { this.basic_food = basic_food; } public String make() { return basic_food.make()+"+麵包"; } } //奶油類 public class Cream extends Food { private Food basic_food; public Cream(Food basic_food) { this.basic_food = basic_food; } public String make() { return basic_food.make()+"+奶油"; } } //蔬菜類 public class Vegetable extends Food { private Food basic_food; public Vegetable(Food basic_food) { this.basic_food = basic_food; } public String make() { return basic_food.make()+"+蔬菜"; } }
這幾個類都是差不多的,構造方法傳入一個Food型別的引數,然後在make方法中加入一些自己的邏輯,如果你還是看不懂為什麼這麼寫,不急,你看看我的Test類是怎麼寫的,一看你就明白了
public class Test { public static void main(String[] args) { Food food = new Bread(new Vegetable(new Cream(new Food("香腸")))); System.out.println(food.make()); } }
看到沒有,一層一層封裝,我們從裡往外看:最裡面我new了一個香腸,在香腸的外面我包裹了一層奶油,在奶油的外面我又加了一層蔬菜,最外面我放的是麵包,是不是很形象,哈哈~ 這個設計模式簡直跟現實生活中一摸一樣,看懂了嗎? 我們看看執行結果吧
執行結果
一個三明治就做好了~
介面卡模式
將兩種完全不同的事物聯絡到一起,就像現實生活中的變壓器。假設一個手機充電器需要的電壓是20V,但是正常的電壓是220V,這時候就需要一個變壓器,將220V的電壓轉換成20V的電壓,這樣,變壓器就將20V的電壓和手機聯絡起來了。
public class Test { public static void main(String[] args) { Phone phone = new Phone(); VoltageAdapter adapter = new VoltageAdapter(); phone.setAdapter(adapter); phone.charge(); } } // 手機類 class Phone { public static final int V = 220;// 正常電壓220v,是一個常量 private VoltageAdapter adapter; // 充電 public void charge() { adapter.changeVoltage(); } public void setAdapter(VoltageAdapter adapter) { this.adapter = adapter; } } // 變壓器 class VoltageAdapter { // 改變電壓的功能 public void changeVoltage() { System.out.println("正在充電..."); System.out.println("原始電壓:" + Phone.V + "V"); System.out.println("經過變壓器轉換之後的電壓:" + (Phone.V - 200) + "V"); } }
工廠模式
簡單工廠模式:一個抽象的介面,多個抽象介面的實現類,一個工廠類,用來例項化抽象的介面
// 抽象產品類 abstract class Car { public void run(); public void stop(); } // 具體實現類 class Benz implements Car { public void run() { System.out.println("Benz開始啟動了。。。。。"); } public void stop() { System.out.println("Benz停車了。。。。。"); } } class Ford implements Car { public void run() { System.out.println("Ford開始啟動了。。。"); } public void stop() { System.out.println("Ford停車了。。。。"); } } // 工廠類 class Factory { public static Car getCarInstance(String type) { Car c = null; if ("Benz".equals(type)) { c = new Benz(); } if ("Ford".equals(type)) { c = new Ford(); } return c; } } public class Test { public static void main(String[] args) { Car c = Factory.getCarInstance("Benz"); if (c != null) { c.run(); c.stop(); } else { System.out.println("造不了這種汽車。。。"); } } } 工廠方法模式:有四個角色,抽象工廠模式,具體工廠模式,抽象產品模式,具體產品模式。不再是由一個工廠類去例項化具體的產品,而是由抽象工廠的子類去例項化產品 // 抽象產品角色 public interface Moveable { void run(); } // 具體產品角色 public class Plane implements Moveable { @Override public void run() { System.out.println("plane...."); } } public class Broom implements Moveable { @Override public void run() { System.out.println("broom....."); } } // 抽象工廠 public abstract class VehicleFactory { abstract Moveable create(); } // 具體工廠 public class PlaneFactory extends VehicleFactory { public Moveable create() { return new Plane(); } } public class BroomFactory extends VehicleFactory { public Moveable create() { return new Broom(); } } // 測試類 public class Test { public static void main(String[] args) { VehicleFactory factory = new BroomFactory(); Moveable m = factory.create(); m.run(); } }
抽象工廠模式:與工廠方法模式不同的是,工廠方法模式中的工廠只生產單一的產品,而抽象工廠模式中的工廠生產多個產品
/抽象工廠類 public abstract class AbstractFactory { public abstract Vehicle createVehicle(); public abstract Weapon createWeapon(); public abstract Food createFood(); } //具體工廠類,其中Food,Vehicle,Weapon是抽象類, public class DefaultFactory extends AbstractFactory{ @Override public Food createFood() { return new Apple(); } @Override public Vehicle createVehicle() { return new Car(); } @Override public Weapon createWeapon() { return new AK47(); } } //測試類 public class Test { public static void main(String[] args) { AbstractFactory f = new DefaultFactory(); Vehicle v = f.createVehicle(); v.run(); Weapon w = f.createWeapon(); w.shoot(); Food a = f.createFood(); a.printName(); } }
代理模式(proxy)
有兩種,靜態代理和動態代理。先說靜態代理,很多理論性的東西我不講,我就算講了,你們也看不懂。什麼真實角色,抽象角色,代理角色,委託角色。。。亂七八糟的,我是看不懂。之前學代理模式的時候,去網上翻一下,資料一大堆,開啟連結一看,基本上都是給你分析有什麼什麼角色,理論一大堆,看起來很費勁,不信的話你們可以去看看,我是看不懂他們在說什麼。咱不來虛的,直接用生活中的例子說話。(注意:我這裡並不是否定理論知識,我只是覺得有時候理論知識晦澀難懂,喜歡挑刺的人一邊去,你是來學習知識的,不是來挑刺的)
到了一定的年齡,我們就要結婚,結婚是一件很麻煩的事情,(包括那些被父母催婚的)。有錢的家庭可能會找司儀來主持婚禮,顯得熱鬧,洋氣~好了,現在婚慶公司的生意來了,我們只需要給錢,婚慶公司就會幫我們安排一整套結婚的流程。整個流程大概是這樣的:家裡人催婚->男女雙方家庭商定結婚的黃道即日->找一家靠譜的婚慶公司->在約定的時間舉行結婚儀式->結婚完畢
婚慶公司打算怎麼安排婚禮的節目,在婚禮完畢以後婚慶公司會做什麼,我們一概不知。。。別擔心,不是黑中介,我們只要把錢給人家,人家會把事情給我們做好。所以,這裡的婚慶公司相當於代理角色,現在明白什麼是代理角色了吧。
程式碼實現請看:
//代理介面 public interface ProxyInterface { //需要代理的是結婚這件事,如果還有其他事情需要代理,比如吃飯睡覺上廁所,也可以寫 void marry(); //代理吃飯(自己的飯,讓別人吃去吧) //void eat(); //代理拉屎,自己的屎,讓別人拉去吧 //void shit(); }
文明社會,代理吃飯,代理拉屎什麼的我就不寫了,有傷社會風化~~~能明白就好
好了,我們看看婚慶公司的程式碼:
public class WeddingCompany implements ProxyInterface { private ProxyInterface proxyInterface; public WeddingCompany(ProxyInterface proxyInterface) { this.proxyInterface = proxyInterface; } @Override public void marry() { System.out.println("我們是婚慶公司的"); System.out.println("我們在做結婚前的準備工作"); System.out.println("節目彩排..."); System.out.println("禮物購買..."); System.out.println("工作人員分工..."); System.out.println("可以開始結婚了"); proxyInterface.marry(); System.out.println("結婚完畢,我們需要做後續處理,你們可以回家了,其餘的事情我們公司來做"); } }
看到沒有,婚慶公司需要做的事情很多,我們再看看結婚家庭的程式碼:
public class NormalHome implements ProxyInterface{ @Override public void marry() { System.out.println("我們結婚啦~"); } }
這個已經很明顯了,結婚家庭只需要結婚,而婚慶公司要包攬一切,前前後後的事情都是婚慶公司來做,聽說現在婚慶公司很賺錢的,這就是原因,乾的活多,能不賺錢嗎?
來看看測試類程式碼:
public class Test { public static void main(String[] args) { ProxyInterface proxyInterface = new WeddingCompany(new NormalHome()); proxyInterface.marry(); } }
執行結果如下:
生產者/消費者模式
什麼是生產者/消費者模式?
某個模組負責產生資料,這些資料由另一個模組來負責處理(此處的模組是廣義的,可以是類、函式、執行緒、程序等)。產生資料的模組,就形象地稱為生產者;而處理資料的模組,就稱為消費者。在生產者與消費者之間在加個緩衝區,我們形象的稱之為倉庫,生產者負責往倉庫了進商品,而消費者負責從倉庫裡拿商品,這就構成了生產者消費者模式。結構圖如下:
生產者消費者模式有如下幾個優點:
1、解耦
由於有緩衝區的存在,生產者和消費者之間不直接依賴,耦合度降低。
2、支援併發
由於生產者與消費者是兩個獨立的併發體,他們之間是用緩衝區作為橋樑連線,生產者只需要往緩衝區裡丟資料,就可以繼續生產下一個資料,而消費者只需要從緩衝區了拿資料即可,這樣就不會因為彼此的處理速度而發生阻塞。
3、支援忙閒不均
緩衝區還有另一個好處。如果製造資料的速度時快時慢,緩衝區的好處就體現出來 了。當資料製造快的時候,消費者來不及處理,未處理的資料可以暫時存在緩衝區中。 等生產者的製造速度慢下來,消費者再慢慢處理掉。
生產者-消費者模型準確說應該是“生產者-倉儲-消費者”模型,這樣的模型遵循如下的規則:
1、生產者僅僅在倉儲未滿時候生產,倉滿則停止生產。
2、消費者僅僅在倉儲有產品時候才能消費,倉空則等待。
3、當消費者發現倉儲沒產品可消費時候會通知生產者生產。
4、生產者在生產出可消費產品時候,應該通知等待的消費者去消費
此模型將要結合java.lang.Object的wait與notify、notifyAll方法來實現以上的需求。例項程式碼如下:
建立所謂的“倉庫”,此類是(本質上:共同訪問的)共享資料區域
//此類是(本質上:共同訪問的)共享資料區域 public class SyncStack { private String[] str = new String[10]; private int index; //供生產者呼叫 public synchronized void push(String sst){ if(index == sst.length()){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.notify();//喚醒在此物件監視器上等待的單個執行緒 str[index] = sst; index++; } //供消費者呼叫 public synchronized String pop(){ if(index == 0){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.notify(); index--; String product = str[index]; return product; } //就是定義一個返回值為陣列的方法,返回的是一個String[]引用 public String[] pro(){ return str; } }
建立生產者:
public class Producter implements Runnable { private SyncStack stack; public Producter(SyncStack stack){ this.stack = stack; } public void run(){ for(int i = 0;i<stack.pro().length;i++){ String producter = "產品" + i; stack.push(producter); System.out.println("生產了:" + producter); try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
建立消費者:
public class Consumer implements Runnable { private SyncStack stack; public Consumer(SyncStack stack){ this.stack = stack; } public void run(){ for(int i=0;i<stack.pro().length;i++){ String consumer = stack.pop(); System.out.println("消費了:" + consumer ); try { Thread.sleep(400); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
測試類:
public class TestDeam { public static void main(String[] args) { SyncStack stack = new SyncStack(); Consumer p = new Consumer(stack); Producter c = new Producter(stack); new Thread(p).start(); new Thread(c).start(); } }
測試結果:
生產了:產品0 消費了:產品0 生產了:產品1 生產了:產品2 消費了:產品2 生產了:產品3 消費了:產品3 生產了:產品4 生產了:產品5 生產了:產品6 消費了:產品5 生產了:產品7 消費了:產品6 消費了:產品7 生產了:產品8 生產了:產品9 消費了:產品8 消費了:產品9 消費了:產品4 消費了:產品1
十種常用的設計模式
最近發現一個網站對設計模式講解的非常有深度 點這裡設計模式
1.單例模式:
實現方式:
a) 將被實現的類的構造方法設計成private的。
b) 新增此類引用的靜態成員變數,併為其例項化。
c) 在被實現的類中提供公共的CreateInstance函式,返回例項化的此類,就是b中的靜態成員變數。
應用場景:
優點:
1.在單例模式中,活動的單例只有一個例項,對單例類的所有例項化得到的都是相同的一個例項。這樣就 防止其它物件對自己的例項化,確保所有的物件都訪問一個例項
2.單例模式具有一定的伸縮性,類自己來控制例項化程序,類就在改變例項化程序上有相應的伸縮性。
3.提供了對唯一例項的受控訪問。
4.由於在系統記憶體中只存在一個物件,因此可以 節約系統資源,當 需要頻繁建立和銷燬的物件時單例模式無疑可以提高系統的效能。
5.允許可變數目的例項。
6.避免對共享資源的多重佔用。
缺點:
1.不適用於變化的物件,如果同一型別的物件總是要在不同的用例場景發生變化,單例就會引起資料的錯誤,不能儲存彼此的狀態。
2.由於單利模式中沒有抽象層,因此單例類的擴充套件有很大的困難。
3.單例類的職責過重,在一定程度上違背了“單一職責原則”。
4.濫用單例將帶來一些負面問題,如為了節省資源將資料庫連線池物件設計為的單例類,可能會導致共享連線池物件的程式過多而出現連線池溢位;如果例項化的物件長時間不被利用,系統會認為是垃圾而被回收,這將導致物件狀態的丟失。
使用注意事項:
1.使用時不能用反射模式建立單例,否則會例項化一個新的物件
2.使用懶單例模式時注意執行緒安全問題
3.單例模式和懶單例模式構造方法都是私有的,因而是不能被繼承的,有些單例模式可以被繼承(如登記式模式)
適用場景:
單例模式只允許建立一個物件,因此節省記憶體,加快物件訪問速度,因此物件需要被公用的場合適合使用,如多個模組使用同一個資料來源連線物件等等。如:
1.需要頻繁例項化然後銷燬的物件。
2.建立物件時耗時過多或者耗資源過多,但又經常用到的物件。
3.有狀態的工具類物件。
4.頻繁訪問資料庫或檔案的物件。
以下都是單例模式的經典使用場景:
1.資源共享的情況下,避免由於資源操作時導致的效能或損耗等。如上述中的日誌檔案,應用配置。
2.控制資源的情況下,方便資源之間的互相通訊。如執行緒池等。
應用場景舉例:
1.外部資源:每臺計算機有若干個印表機,但只能有一個PrinterSpooler,以避免兩個列印作業同時輸出到印表機。內部資源:大多數軟體都有一個(或多個)屬性檔案存放系統配置,這樣的系統應該有一個物件管理這些屬性檔案
2. Windows的TaskManager(工作管理員)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能開啟兩個windows task manager嗎? 不信你自己試試看哦~
3. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統執行過程中,回收站一直維護著僅有的一個例項。
4. 網站的計數器,一般也是採用單例模式實現,否則難以同步。
5. 應用程式的日誌應用,一般都何用單例模式實現,這一般是由於共享的日誌檔案一直處於開啟狀態,因為只能有一個例項去操作,否則內容不好追加。
6. Web應用的配置物件的讀取,一般也應用單例模式,這個是由於配置檔案是共享的資源。
7. 資料庫連線池的設計一般也是採用單例模式,因為資料庫連線是一種資料庫資源。資料庫軟體系統中使用資料庫連線池,主要是節省開啟或者關閉資料庫連線所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因為何用單例模式來維護,就可以大大降低這種損耗。
8. 多執行緒的執行緒池的設計一般也是採用單例模式,這是由於執行緒池要方便對池中的執行緒進行控制。
9. 作業系統的檔案系統,也是大的單例模式實現的具體例子,一個作業系統只能有一個檔案系統。
10. HttpApplication 也是單位例的典型應用。熟悉ASP.Net(IIS)的整個請求生命週期的人應該知道HttpApplication也是單例模式,所有的HttpModule都共享一個HttpApplication例項.
2.策略模式:
實現方式:
a) 提供公共介面或抽象類,定義需要使用的策略方法。(策略抽象類)
b) 多個實現的策略抽象類的實現類。(策略實現類)
c) 環境類,對多個實現類的封裝,提供介面型別的成員量,可以在客戶端中切換。
d) 客戶端 呼叫環境類 進行不同策略的切換。
注:Jdk中的TreeSet和TreeMap的排序功能就是使用了策略模式。
策略模式的優點
(1)策略模式提供了管理相關的演算法族的辦法。策略類的等級結構定義了一個演算法或行為族。恰當使用繼承可以把公共的程式碼移到父類裡面,從而避免程式碼重複。
(2)使用策略模式可以避免使用多重條件(if-else)語句。多重條件語句不易維護,它把採取哪一種演算法或採取哪一種行為的邏輯與演算法或行為的邏輯混合在一起,統統列在一個多重條件語句裡面,比使用繼承的辦法還要原始和落後。
策略模式的缺點
(1)客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味著客戶端必須理解這些演算法的區別,以便適時選擇恰當的演算法類。換言之,策略模式只適用於客戶端知道演算法或行為的情況。
(2)由於策略模式把每個具體的策略實現都單獨封裝成為類,如果備選的策略很多的話,那麼物件的數目就會很可觀。
3.代理模式:
一)靜態代理
實現方式:
a) 為真實類和代理類提供的公共介面或抽象類。(租房)
b) 真實類,具體實現邏輯,實現或繼承a。(房主向外租房)
c) 代理類,實現或繼承a,有對b的引用,呼叫真實類的具體實現。(中介)
d) 客戶端,呼叫代理類實現對真實類的呼叫。(租客租房)
二)動態代理
實現方式:
a) 公共的介面(必須是介面,因為Proxy類的newproxyinstance方法的第二引數必須是個介面型別的Class)
b) 多個真實類,具體實現的業務邏輯。
c) 代理類,實現InvocationHandler介面,提供Object成員變數,和Set方法,便於客戶端切換。
d) 客戶端,獲得代理類的例項,為object例項賦值,呼叫Proxy.newproxyinstance方法在程式執行時生成繼承公共介面的例項,呼叫相應方法,此時方法的執行由代理類實現的Invoke方法接管。
jdk動態代理使用的侷限性:
通過反射類Proxy
和InvocationHandler
回撥介面實現的jdk動態代理,要求委託類必須實現一個介面,但事實上並不是所有類都有介面,對於沒有實現介面的類,便無法使用該方方式實現動態代理。
4.觀察者模式:
觀察者模式是物件的行為模式,又叫釋出-訂閱(Publish/Subscribe)模式、模型-檢視(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
實現方式:
a) 角色抽象類(提供對觀察者的新增,刪除和通知功能)。
b) 角色具體類,實現a,維護一個c的集合(對角色抽象類的實現)。
c) 觀察者抽象類(被角色通知後實現的方法)。
d) 觀察者實現類,實現c(多個)。
注:JDK提供了對觀察者模式的支援,使用Observable類和Observer介面
兩種模型(推模型和拉模型):
■ 推模型是假定主題物件知道觀察者需要的資料;而拉模型是主題物件不知道觀察者具體需要什麼資料,沒有辦法的情況下,乾脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
■ 推模型可能會使得觀察者物件難以複用,因為觀察者的update()方法是按需要定義的引數,可能無法兼顧沒有考慮到的使用情況。這就意味著出現新情況的時候,就可能提供新的update()方法,或者是乾脆重新實現觀察者;而拉模型就不會造成這樣的情況,因為拉模型下,update()方法的引數是主題物件本身,這基本上是主題物件能傳遞的最大資料集合了,基本上可以適應各種情況的需要。
5.裝飾模式:
實現方式:
a)抽象的被裝飾角色 (所有的角色都要直接或間接的實現本角色)
b)具體的被裝飾角色,實現或繼承a (被功能擴充套件的角色)
c)裝飾角色,實現或繼承a (本類有對a的引用,所有的具體裝飾角色都需要繼承這個角色)
d)多個具體修飾角色 ,繼承c(對被裝飾角色的功能擴充套件,可以任意搭配使用)
意圖:
動態地給一個物件新增一些額外的職責。就增加功能來說,Decorator模式相比生成子類更為靈活。該模式以對客 戶端透明的方式擴充套件物件的功能。
適用環境:
(1)在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。
(2)處理那些可以撤消的職責。
(3)當不能採用生成子類的方法進行擴充時。一種情況是,可能有大量獨立的擴充套件,為支援每一種組合將產生大量的 子類,使得子類數目呈爆炸性增長。另一種情況可能是因為類定義被隱藏,或類定義不能用於生成子類。
6.介面卡模式:
介面卡模式把一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠在一起工作。
1. 類介面卡(子類繼承方式)
實現方式:
a)目標抽象角色(定義客戶要用的介面)
b)介面卡(實現a繼承c,作為一個轉換器被客戶呼叫)
c)待介面卡(真正需要被呼叫的)
d)客戶端(借用a的例項呼叫c的方法)
2.物件介面卡(物件的組合方式)
實現方式:
a)目標抽象角色(定義客戶要用的介面)
b)介面卡(實現a,維護一個c的引用,作為一個轉換器被d呼叫)
c)待介面卡(真正需要被呼叫的)
d)客戶端(此類,借用a類的例項呼叫c類的方法,類似靜態代理,但是解決的問題不同)
3.預設的方式
實現方式:
a)抽象介面
b)實現a的介面卡類(空實現)
c)客戶端,繼承b,呼叫b中的方法,不必直接實現a(直接實現a需要實現a中的所有的方法)
介面卡模式的優點:
1.更好的複用性
系統需要使用現有的類,而此類的介面不符合系統的需要。那麼通過介面卡模式就可以讓這些功能得到更好的複用。
2.更好的擴充套件性
在實現介面卡功能的時候,可以呼叫自己開發的功能,從而自然地擴充套件系統的功能。
介面卡模式的缺點:
過多的使用介面卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到呼叫的是A介面,其實內部被適配成了B介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用介面卡,而是直接對系統進行重構。
7. 命令模式
將一個請求封裝為一個物件,從而可用不同的請求對客戶進行引數化;對請求排隊或記錄日誌,以及支援可撤銷的操作
將“發出請求的物件”和”接收與執行這些請求的物件”分隔開來。
實現方式:
a)抽象的命令角色 , 如:選單(規定可以點哪些菜)
b)具體的命令角色(實現a 維護一個對c的引用),如:訂單(已點的菜)
c)接收者(具體執行命令的角色),實際操作時,很常見使用"聰明"命令物件,也就是直接實現了請求,而不是將工作委託給c (弊端?) 如:廚師接收訂單後做菜
d)呼叫者(維護一個對a的引用),如:服務員負責點菜並把訂單推給廚師
e)客戶端 呼叫d發出命令進而執行c的方法,如:顧客點餐
效果:
1)、command模式將呼叫操作的物件和實現該操作的物件解耦
2)、可以將多個命令裝配成一個複合命令,複合命令是Composite模式的一個例項
3)、增加新的command很容易,無需改變已有的類
適用性:
1)、抽象出待執行的動作以引數化某物件
2)、在不同的時刻指定、排列和執行請求。如請求佇列
3)、支援取消操作
4)、支援修改日誌
5)、用構建在原語操作上的高層操作構造一個系統。支援事物
8. 組合模式
將物件組合成樹形結構以表示“部分整體”的層次結構。組合模式使得使用者對單個物件和複雜物件的使用具有一致性。
實現方式:
a)抽象的構件介面 (規範執行的方法),b及c都需實現此介面,如:Junit中的Test介面
b)葉部件(實現a,最小的執行單位),如:Junit中我們所編寫的測試用例
c)組合類(實現a並維護一個a的集合[多個b的組合]),如:Junit中的 TestSuite
d)客戶端 可以隨意的將b和c進行組合,進行呼叫
什麼情況下使用組合模式:
當發現需求中是體現部分與整體層次結構時,以及你希望使用者可以忽略組合物件與單個物件的不同,統一地使用組合結構中的所有物件時,就應該考慮組合模式了。
9. 簡單工廠模式
就是建立一個工廠類,對實現了同一介面的一些類進行例項的建立。簡單工廠模式的實質是由一個工廠類根據傳入的引數,動態決定應該建立哪一個產品類(這些產品類繼承自一個父類或介面)的例項。
實現方式:
a)抽象產品類(也可以是介面)
b)多個具體的產品類
c)工廠類(包括建立a的例項的方法)
優點:
工廠類是整個模式的關鍵.包含了必要的邏輯判斷,根據外界給定的資訊,決定究竟應該建立哪個具體類的物件.通過使用工廠類,外界可以從直接建立具體產品物件的尷尬局面擺脫出來,僅僅需要負責“消費”物件就可以了。而不必管這些物件究竟如何建立及如何組織的.明確了各自的職責和權利,有利於整個軟體體系結構的優化。
缺點:
由於工廠類集中了所有例項的建立邏輯,違反了高內聚責任分配原則,將全部建立邏輯集中到了一個工廠類中;它所能建立的類只能是事先考慮到的,如果需要新增新的類,則就需要改變工廠類了。當系統中的具體產品類不斷增多時候,可能會出現要求工廠類根據不同條件建立不同例項的需求.這種對條件的判斷和對具體產品型別的判斷交錯在一起,很難避免模組功能的蔓延,對系統的維護和擴充套件非常不利;
10. 模板方法模式
實現方式:
a)父類模板類(規定要執行的方法和順序,只關心方法的定義及順序,不關心方法實現)
b)子類實現類(實現a規定要執行的方法,只關心方法實現,不關心呼叫順序)
優點:
1)封裝不變部分,擴充套件可變部分:把認為不變部分的演算法封裝到父類實現,可變部分則可以通過繼承來實現,很容易擴充套件。
2)提取公共部分程式碼,便於維護。
3)行為由父類控制,由子類實現。
缺點:
模板方法模式顛倒了我們平常的設計習慣:抽象類負責宣告最抽象、最一般的事物屬性和方法,實現類實現具體的事物屬性和方法。在複雜的專案中可能會帶來程式碼閱讀的難度。