前言:
在Java中,傳說有23中模式,總共分為三大類,分別是:
- 創(chuàng)建型模式(5種):工廠方法模式、抽象工廠模式、建造者模式、單例模式、原型模式;
- 結(jié)構(gòu)型模式(7種):適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式;
- 行為型模式(11種):策略模式、模板方法模式、觀察者模式、迭代子模式、責(zé)任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式。
下面就一起來學(xué)習(xí)一下上面字體加粗了的那些設(shè)計模式。
歡迎大家關(guān)注我的公眾號 javawebkf,目前正在慢慢地將簡書文章搬到公眾號,以后簡書和公眾號文章將同步更新,且簡書上的付費文章在公眾號上將免費。
一、工廠方法模式:
工廠是干嘛的,就是用來生產(chǎn)的嘛,這里說的工廠也是用來生產(chǎn)的,它是用來生產(chǎn)對象的。也就是說,有些對象我們可以在工廠里面生產(chǎn),需要用時直接從工廠里面拿出來即可,而不用每次需要用的時候都去new對象。工廠方法模式又分為以下三種:
- 普通工廠模式:就是建立一個工廠類,對實現(xiàn)了同一接口的一些類進(jìn)行實例的創(chuàng)建。
- 多個工廠方法模式:是對普通工廠方法模式的改進(jìn),在普通工廠方法模式中,如果傳遞的字符串出錯,則不能正確創(chuàng)建對象,而多個工廠方法模式是提供多個工廠方法,分別創(chuàng)建對象。
- 靜態(tài)工廠方法模式:將上面的多個工廠方法模式里的方法置為靜態(tài)的,不需要創(chuàng)建實例,直接調(diào)用即可。
上面三個模式中,后一個都是對前一個的改良。下面分別看看這三個模式的具體案例。
情景:有一個發(fā)送消息的接口,有兩個實現(xiàn)類,一個是發(fā)送短信,一個是發(fā)送郵件。代碼如下:
// 接口
public interface Sender {
public void Send();
}
//實現(xiàn)一
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mail sender!");
}
}
//實現(xiàn)二
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
下面就看看用這三種工廠方法模式分別要怎么做。
- 普通工廠模式:
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("請輸入正確的類型!");
return null;
}
}
}
使用的時候就創(chuàng)建這個工廠的對象,調(diào)用produce方法,需要發(fā)郵件就傳入"mail",需要發(fā)短信就傳入"sms",如果傳入的是別的內(nèi)容,就不會創(chuàng)建任何對象。
- 多個工廠方法模式:
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
普通工廠模式生產(chǎn)對象都是在一個方法內(nèi)完成,多個工廠方法模式是提供多個方法,分別生產(chǎn)對應(yīng)的對象。使用時先創(chuàng)建工廠的實例,要發(fā)短信就調(diào)用生產(chǎn)短信實例的方法,要發(fā)郵件就調(diào)用生產(chǎn)郵件實例的方法。
- 靜態(tài)工廠方法模式:
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
就是多個工廠方法模式里面的方法設(shè)為靜態(tài)的,這樣可以直接通過工廠類的類名調(diào)用,更加方便。
二、抽象工廠模式:
上面的工廠方法模式有一個缺點,就是類的創(chuàng)建依賴工廠類,比如現(xiàn)在還可以用微信發(fā)消息,那么就得在工廠類中新增一個創(chuàng)建WeChat實體的方法。這樣就違背了開閉原則。如何解決?就用到抽象工廠模式,創(chuàng)建多個工廠類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,就不需要修改之前的工廠類代碼。 上面的案例代碼就可以修改成下面這樣:
// 工廠類接口
public interface Provider {
public Sender produce();
}
//生產(chǎn)SmsSender對象的工廠
public class SendSmsFactory implements Provider {
@Override
public Sender produce() {
return new SmsSender();
}
}
//生產(chǎn)MailSender對象的工廠
public class SendMailFactory implements Provider {
@Override
public Sender produce() {
return new MailSender();
}
}
// 測試
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.send();
}
}
這樣即使要新增生產(chǎn)WechatSender實例的方法,也不需要修改現(xiàn)有代碼,只需實現(xiàn)工廠接口,重寫生產(chǎn)方法即可。從工廠方法模式到抽象工廠模式,后者更加體現(xiàn)了Java的封裝、抽象等思想。
三、建造者模式:
工廠模式提供的是創(chuàng)建單個類實例的模式,而建造者模式可以理解為是批量生產(chǎn)。還是使用工廠方法模式中的情景,看看用建造者模式怎么實現(xiàn):
// 建造類
public class Builder {
private List<Sender> list = new ArrayList<Sender>();
public void produceMailSender(int count) { // 生產(chǎn)count個MailSender
for (int i = 0; i < count; i++) {
list.add(new MailSender());
}
}
public void produceSmsSender(int count) { // 生產(chǎn)count個SmsSender
for (int i = 0; i < count; i++) {
list.add(new SmsSender());
}
}
}
/* =========================== 使用 ============================*/
public class TestBuilder {
public static void main(String[] args) {
Builder builder = new Builder();
builder.produceMailSender(10); // 生產(chǎn)10個MailSender
}
}
四、單例模式:
什么叫單例,就是保證一個類在內(nèi)存中只有一個對象。Runtime()方法就是單例設(shè)計模式進(jìn)行設(shè)計的。如何保證內(nèi)存中只有一個對象呢?
設(shè)計思路:
- 不讓其他程序創(chuàng)建該類對象。
- 在本類中創(chuàng)建一個本類對象。
- 對外提供方法,讓其他程序獲取這個對象。
實現(xiàn)步驟:
- 私有化構(gòu)造函數(shù);
- 創(chuàng)建私有并靜態(tài)的本類對象;
- 定義公有并靜態(tài)的方法,返回該對象。
代碼實現(xiàn):
- 懶漢式:延遲加載
class Single{
private Single(){} // 將構(gòu)造方法私有化
private static Single s = null; // 私有靜態(tài)的本類對象
public static synchronized Single getInstance(){ // 靜態(tài)公共的返回對象的方法
if(s==null)
s = new Single();
return s;
}
}
- 餓漢式:
class Single{
private Single(){} //私有化構(gòu)造函數(shù)。
private static Single s = new Single(); //創(chuàng)建私有并靜態(tài)的本類對象。
public static Single getInstance(){ //定義公有并靜態(tài)的方法,返回該對象。
return s;
}
}
五、適配器模式:
適配器模式將某個類的接口轉(zhuǎn)換成客戶端期望的另一個接口表示,目的是消除由于接口不匹配所造成的類的兼容性問題。主要分為以下三類:
- 類的適配器模式:比如一個類有一個方法method1,但是客戶端使用的時候還需要一個method2方法,那就可以將method1和method2方法寫進(jìn)接口中,然后新建一個適配器類繼承原來的類并實現(xiàn)這個接口。
- 對象的適配器模式:與類適配器相比,不需再繼承source類,而是將source的對象傳過去。
- 接口的適配器模式
看一下具體用代碼怎么體現(xiàn):
- 類的適配器模式:
public class Source {
public void method1() {
System.out.println("這是method1方法");
}
}
// 新建接口
public interface Targetable {
/* 與原類中的方法相同 */
public void method1();
/* 新類的方法 */
public void method2();
}
// 適配類
public class Adapter extends Source implements Targetable {
@Override
public void method2() {
System.out.println("這是method2方法");
}
}
/* ===================== 使用 ======================*/
public class AdapterTest {
public static void main(String[] args) {
Targetable target = new Adapter();
target.method1();
target.method2();
}
}
- 對象的適配器模式:
public class Source {
public void method1() {
System.out.println("這是method1方法");
}
}
public interface Targetable {
/* 與原類中的方法相同 */
public void method1();
/* 新類的方法 */
public void method2();
}
// 適配類
public class Wrapper implements Targetable {
private Source source;
public Wrapper(Source source) {
super();
this.source = source;
}
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
@Override
public void method1() {
source.method1();
}
}
/* ======================= 使用 =========================*/
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source();
Targetable target = new Wrapper(source);
target.method1();
target.method2();
}
}
與類的適配器模式不同的是,對象的適配器模式不再繼承source類,而是直接將source對象傳到Wrapper類就可以了。
- 接口的適配器模式
接口的適配器是這樣的:有時我們寫的一個接口中有多個抽象方法,當(dāng)我們寫該接口的實現(xiàn)類時,必須實現(xiàn)該接口的所有方法,這明顯有時比較浪費,因為并不是所有的方法都是我們需要的,有時只需要某一些,此處為了解決這個問題,我們引入了接口的適配器模式,借助于一個抽象類,該抽象類實現(xiàn)了該接口,實現(xiàn)了所有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯(lián)系,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行。
六、裝飾器模式:
裝飾模式就是給一個對象動態(tài)的增加一些新的功能。要求裝飾對象和被裝飾對象實現(xiàn)同一個接口,裝飾對象持有被裝飾對象的實例。 這樣說得也很抽象,看看具體的案例:
// 接口
public interface Sourceable {
public void method();
}
// 被裝飾的類,實現(xiàn)Sourceable接口
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
// 裝飾的類,也要實現(xiàn)Sourceable 接口
public class Decorator implements Sourceable {
private Sourceable source; // 持有被裝飾類的對象
public Decorator(Sourceable source) {
super();
this.source = source;
}
@Override
public void method() {
System.out.println("before decorator!"); // 裝飾
source.method();
System.out.println("after decorator!"); // 裝飾
}
}
// 測試
public class DecoratorTest {
public static void main(String[] args) {
Sourceable source = new Source();
Sourceable obj = new Decorator(source);
obj.method();
}
}
IO流體系中很多類就用到了這種設(shè)計模式。
七、策略模式:
策略模式是對算法的包裝,是把使用算法的責(zé)任和算法本身分割開來,委派給不同的對象管理。策略模式通常把一個系列的算法包裝到一系列的策略類里面,作為一個抽象策略類的子類??纯淳唧w的代碼:
// 策略接口
public interface Strategy {
public void strategyInterface();
}
// 具體策略類A
public class ConcreteStrategyA implements Strategy {
@Override
public void strategyInterface() {
// 相關(guān)的業(yè)務(wù)
}
}
// 具體策略類B
public class ConcreteStrategyB implements Strategy {
@Override
public void strategyInterface() {
//相關(guān)的業(yè)務(wù)
}
}
// 使用策略的類
public class Context {
public Context(Strategy strategy){ // 構(gòu)造函數(shù),傳入一個具體策略對象
this.strategy = strategy;
}
public void contextInterface(){ // 策略方法
strategy.strategyInterface();
}
}
在創(chuàng)建Context類對象的時候,需要使用哪個策略就傳入該策略,然后就可以使用。
八、模板方法模式:
定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。簡單的說就是很多相同的步驟,只是在某一些地方有差別,那么就可以使用這種模式。看例子:
- 獲取一段程序運行時間的模板:
public abstract class GetTime{
public long getTime(){
long start = System.currentTimeMillis;
//表示要計算運行時間的代碼
code();
long end = System.currentTimeMillis;
return end-start;
}
public abstract void code();
}
- 使用該模板:
public class forDemo extends GetTime{
//重寫抽象方法
public void code(){
for(int x=0;x<1000;x++){
System.out.println(x);
}
}
}
- 測試:
public class test{
GetTime gt=new forDemo();
gt.getTime();
}
這樣就可以計算那個for循環(huán)運行的時間了。
九、觀察者模式:
在對象之間定義了一對多的依賴,這樣一來,當(dāng)一個對象改變狀態(tài),依賴它的對象會收到通知并自動更新,這就是觀察者模式。
情景:有一個微信公眾號服務(wù),不定時發(fā)布一些消息,關(guān)注公眾號就可以收到推送消息,取消關(guān)注就收不到推送消息。
示例代碼:
- 定義一個被觀察者接口:
public interface BeObserverd {
public void registerObserver(Observer o);// 添加觀察者
public void removeObserver(Observer o);// 刪除觀察者
public void notifyObserver();// 通知觀察者
}
- 定義一個觀察者接口:
public interface Observer {
public void update(String message);// 當(dāng)被觀察者發(fā)出通知時,這個方法就會執(zhí)行
}
- 定義被觀察者,實現(xiàn)了BeObserverd接口,對BeObserverd接口的三個方法進(jìn)行了具體實現(xiàn),同時有一個List集合,用以保存注冊的觀察者,等需要通知觀察者時,遍歷該集合即可。
// 被觀察者,也就是微信公眾號服務(wù)實現(xiàn)了BeObserverd接口,對BeObserverd接口的三個方法進(jìn)行了具體實現(xiàn)
public class WechatServer implements BeObserverd {
private List<Observer> list;
private String message;
public WechatServer() {
list = new ArrayList<Observer>(); // 觀察者的集合
}
// 新增觀察者
@Override
public void registerObserver(Observer o) {
list.add(o);
}
// 刪除觀察者
@Override
public void removeObserver(Observer o) {
if(!list.isEmpty())
list.remove(o);
}
// 給觀察者發(fā)通知
@Override
public void notifyObserver() {
for(int i = 0; i < list.size(); i++) {
Observer observer = list.get(i);
observer.update(message);
}
}
// 模擬公眾號推送消息的方法
public void setInfomation(String s) {
this.message = s;
System.out.println("微信服務(wù)更新消息: " + s);
//消息更新,通知所有觀察者
notifyObserver();
}
}
- 定義具體觀察者,微信公眾號的具體觀察者為用戶User。
public class User implements Observer {
private String name;
private String message;
public User(String name) {
this.name = name;
}
@Override
public void update(String message) {
this.message = message;
read();
}
public void read() {
System.out.println(name + " 收到推送消息: " + message);
}
}
假如現(xiàn)有三個人關(guān)注公眾號,當(dāng)微信公眾號調(diào)用setInfomation方法發(fā)送推文時,通過調(diào)用notifyObserver方法,通知每個觀察者,在notifyObserver方法里調(diào)用update方法,update里面調(diào)用read方法,三個人都能收到推送;加入現(xiàn)在有人取消關(guān)注了,那么就會調(diào)用removeObserver方法,下次推送這個人就收不到了。(本案例參考羅漢果的博文,感謝大神整理!)
總結(jié):
本文聊了九個比較常用的設(shè)計模式,有些模式看文字可能覺得比較抽象難懂,通過案例代碼結(jié)合起來理解會更容易些。剩下的十四個設(shè)計模式,下次再聊。