前言:
在Java中廓译,傳說有23中模式,總共分為三大類债查,分別是:
- 創(chuàng)建型模式(5種):工廠方法模式非区、抽象工廠模式、建造者模式盹廷、單例模式征绸、原型模式;
- 結(jié)構(gòu)型模式(7種):適配器模式俄占、裝飾器模式管怠、代理模式、外觀模式缸榄、橋接模式渤弛、組合模式、享元模式甚带;
- 行為型模式(11種):策略模式她肯、模板方法模式、觀察者模式鹰贵、迭代子模式晴氨、責(zé)任鏈模式、命令模式砾莱、備忘錄模式瑞筐、狀態(tài)模式、訪問者模式腊瑟、中介者模式聚假、解釋器模式。
下面就一起來學(xué)習(xí)一下上面字體加粗了的那些設(shè)計(jì)模式闰非。
歡迎大家關(guān)注我的公眾號(hào) javawebkf膘格,目前正在慢慢地將簡(jiǎn)書文章搬到公眾號(hào),以后簡(jiǎn)書和公眾號(hào)文章將同步更新财松,且簡(jiǎn)書上的付費(fèi)文章在公眾號(hào)上將免費(fèi)瘪贱。
一、工廠方法模式:
工廠是干嘛的辆毡,就是用來生產(chǎn)的嘛菜秦,這里說的工廠也是用來生產(chǎn)的,它是用來生產(chǎn)對(duì)象的舶掖。也就是說球昨,有些對(duì)象我們可以在工廠里面生產(chǎn),需要用時(shí)直接從工廠里面拿出來即可眨攘,而不用每次需要用的時(shí)候都去new對(duì)象主慰。工廠方法模式又分為以下三種:
- 普通工廠模式:就是建立一個(gè)工廠類嚣州,對(duì)實(shí)現(xiàn)了同一接口的一些類進(jìn)行實(shí)例的創(chuàng)建。
- 多個(gè)工廠方法模式:是對(duì)普通工廠方法模式的改進(jìn)共螺,在普通工廠方法模式中该肴,如果傳遞的字符串出錯(cuò),則不能正確創(chuàng)建對(duì)象藐不,而多個(gè)工廠方法模式是提供多個(gè)工廠方法匀哄,分別創(chuàng)建對(duì)象。
- 靜態(tài)工廠方法模式:將上面的多個(gè)工廠方法模式里的方法置為靜態(tài)的佳吞,不需要?jiǎng)?chuàng)建實(shí)例拱雏,直接調(diào)用即可。
上面三個(gè)模式中底扳,后一個(gè)都是對(duì)前一個(gè)的改良。下面分別看看這三個(gè)模式的具體案例贡耽。
情景:有一個(gè)發(fā)送消息的接口衷模,有兩個(gè)實(shí)現(xiàn)類,一個(gè)是發(fā)送短信蒲赂,一個(gè)是發(fā)送郵件阱冶。代碼如下:
// 接口
public interface Sender {
public void Send();
}
//實(shí)現(xiàn)一
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mail sender!");
}
}
//實(shí)現(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("請(qǐng)輸入正確的類型!");
return null;
}
}
}
使用的時(shí)候就創(chuàng)建這個(gè)工廠的對(duì)象滥嘴,調(diào)用produce方法木蹬,需要發(fā)郵件就傳入"mail",需要發(fā)短信就傳入"sms"若皱,如果傳入的是別的內(nèi)容镊叁,就不會(huì)創(chuàng)建任何對(duì)象。
- 多個(gè)工廠方法模式:
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
普通工廠模式生產(chǎn)對(duì)象都是在一個(gè)方法內(nèi)完成走触,多個(gè)工廠方法模式是提供多個(gè)方法晦譬,分別生產(chǎn)對(duì)應(yīng)的對(duì)象。使用時(shí)先創(chuàng)建工廠的實(shí)例互广,要發(fā)短信就調(diào)用生產(chǎn)短信實(shí)例的方法敛腌,要發(fā)郵件就調(diào)用生產(chǎn)郵件實(shí)例的方法。
- 靜態(tài)工廠方法模式:
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
就是多個(gè)工廠方法模式里面的方法設(shè)為靜態(tài)的惫皱,這樣可以直接通過工廠類的類名調(diào)用像樊,更加方便。
二旅敷、抽象工廠模式:
上面的工廠方法模式有一個(gè)缺點(diǎn)生棍,就是類的創(chuàng)建依賴工廠類,比如現(xiàn)在還可以用微信發(fā)消息扫皱,那么就得在工廠類中新增一個(gè)創(chuàng)建WeChat實(shí)體的方法足绅。這樣就違背了開閉原則捷绑。如何解決?就用到抽象工廠模式氢妈,創(chuàng)建多個(gè)工廠類粹污,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了首量,就不需要修改之前的工廠類代碼壮吩。 上面的案例代碼就可以修改成下面這樣:
// 工廠類接口
public interface Provider {
public Sender produce();
}
//生產(chǎn)SmsSender對(duì)象的工廠
public class SendSmsFactory implements Provider {
@Override
public Sender produce() {
return new SmsSender();
}
}
//生產(chǎn)MailSender對(duì)象的工廠
public class SendMailFactory implements Provider {
@Override
public Sender produce() {
return new MailSender();
}
}
// 測(cè)試
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.send();
}
}
這樣即使要新增生產(chǎn)WechatSender實(shí)例的方法,也不需要修改現(xiàn)有代碼加缘,只需實(shí)現(xiàn)工廠接口鸭叙,重寫生產(chǎn)方法即可。從工廠方法模式到抽象工廠模式拣宏,后者更加體現(xiàn)了Java的封裝沈贝、抽象等思想。
三勋乾、建造者模式:
工廠模式提供的是創(chuàng)建單個(gè)類實(shí)例的模式宋下,而建造者模式可以理解為是批量生產(chǎn)。還是使用工廠方法模式中的情景辑莫,看看用建造者模式怎么實(shí)現(xiàn):
// 建造類
public class Builder {
private List<Sender> list = new ArrayList<Sender>();
public void produceMailSender(int count) { // 生產(chǎn)count個(gè)MailSender
for (int i = 0; i < count; i++) {
list.add(new MailSender());
}
}
public void produceSmsSender(int count) { // 生產(chǎn)count個(gè)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個(gè)MailSender
}
}
四学歧、單例模式:
什么叫單例,就是保證一個(gè)類在內(nèi)存中只有一個(gè)對(duì)象各吨。Runtime()方法就是單例設(shè)計(jì)模式進(jìn)行設(shè)計(jì)的枝笨。如何保證內(nèi)存中只有一個(gè)對(duì)象呢?
設(shè)計(jì)思路:
- 不讓其他程序創(chuàng)建該類對(duì)象揭蜒。
- 在本類中創(chuàng)建一個(gè)本類對(duì)象横浑。
- 對(duì)外提供方法,讓其他程序獲取這個(gè)對(duì)象忌锯。
實(shí)現(xiàn)步驟:
- 私有化構(gòu)造函數(shù)伪嫁;
- 創(chuàng)建私有并靜態(tài)的本類對(duì)象;
- 定義公有并靜態(tài)的方法偶垮,返回該對(duì)象张咳。
代碼實(shí)現(xiàn):
- 懶漢式:延遲加載
class Single{
private Single(){} // 將構(gòu)造方法私有化
private static Single s = null; // 私有靜態(tài)的本類對(duì)象
public static synchronized Single getInstance(){ // 靜態(tài)公共的返回對(duì)象的方法
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)的本類對(duì)象似舵。
public static Single getInstance(){ //定義公有并靜態(tài)的方法脚猾,返回該對(duì)象。
return s;
}
}
五砚哗、適配器模式:
適配器模式將某個(gè)類的接口轉(zhuǎn)換成客戶端期望的另一個(gè)接口表示龙助,目的是消除由于接口不匹配所造成的類的兼容性問題。主要分為以下三類:
- 類的適配器模式:比如一個(gè)類有一個(gè)方法method1,但是客戶端使用的時(shí)候還需要一個(gè)method2方法提鸟,那就可以將method1和method2方法寫進(jìn)接口中军援,然后新建一個(gè)適配器類繼承原來的類并實(shí)現(xiàn)這個(gè)接口。
- 對(duì)象的適配器模式:與類適配器相比称勋,不需再繼承source類胸哥,而是將source的對(duì)象傳過去。
- 接口的適配器模式
看一下具體用代碼怎么體現(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();
}
}
- 對(duì)象的適配器模式:
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();
}
}
與類的適配器模式不同的是赡鲜,對(duì)象的適配器模式不再繼承source類空厌,而是直接將source對(duì)象傳到Wrapper類就可以了。
- 接口的適配器模式
接口的適配器是這樣的:有時(shí)我們寫的一個(gè)接口中有多個(gè)抽象方法银酬,當(dāng)我們寫該接口的實(shí)現(xiàn)類時(shí)嘲更,必須實(shí)現(xiàn)該接口的所有方法,這明顯有時(shí)比較浪費(fèi)揩瞪,因?yàn)椴⒉皇撬械姆椒ǘ际俏覀冃枰母畴袝r(shí)只需要某一些,此處為了解決這個(gè)問題壮韭,我們引入了接口的適配器模式北发,借助于一個(gè)抽象類,該抽象類實(shí)現(xiàn)了該接口喷屋,實(shí)現(xiàn)了所有的方法,而我們不和原始的接口打交道瞭恰,只和該抽象類取得聯(lián)系屯曹,所以我們寫一個(gè)類,繼承該抽象類惊畏,重寫我們需要的方法就行恶耽。
六、裝飾器模式:
裝飾模式就是給一個(gè)對(duì)象動(dòng)態(tài)的增加一些新的功能颜启。要求裝飾對(duì)象和被裝飾對(duì)象實(shí)現(xiàn)同一個(gè)接口偷俭,裝飾對(duì)象持有被裝飾對(duì)象的實(shí)例。 這樣說得也很抽象缰盏,看看具體的案例:
// 接口
public interface Sourceable {
public void method();
}
// 被裝飾的類涌萤,實(shí)現(xiàn)Sourceable接口
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
// 裝飾的類,也要實(shí)現(xiàn)Sourceable 接口
public class Decorator implements Sourceable {
private Sourceable source; // 持有被裝飾類的對(duì)象
public Decorator(Sourceable source) {
super();
this.source = source;
}
@Override
public void method() {
System.out.println("before decorator!"); // 裝飾
source.method();
System.out.println("after decorator!"); // 裝飾
}
}
// 測(cè)試
public class DecoratorTest {
public static void main(String[] args) {
Sourceable source = new Source();
Sourceable obj = new Decorator(source);
obj.method();
}
}
IO流體系中很多類就用到了這種設(shè)計(jì)模式口猜。
七负溪、策略模式:
策略模式是對(duì)算法的包裝,是把使用算法的責(zé)任和算法本身分割開來济炎,委派給不同的對(duì)象管理川抡。策略模式通常把一個(gè)系列的算法包裝到一系列的策略類里面,作為一個(gè)抽象策略類的子類须尚⊙碌蹋看看具體的代碼:
// 策略接口
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ù)侍咱,傳入一個(gè)具體策略對(duì)象
this.strategy = strategy;
}
public void contextInterface(){ // 策略方法
strategy.strategyInterface();
}
}
在創(chuàng)建Context類對(duì)象的時(shí)候,需要使用哪個(gè)策略就傳入該策略密幔,然后就可以使用楔脯。
八、模板方法模式:
定義一個(gè)操作中的算法的骨架老玛,而將一些步驟延遲到子類中淤年。模板方法使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。簡(jiǎn)單的說就是很多相同的步驟蜡豹,只是在某一些地方有差別麸粮,那么就可以使用這種模式【盗看例子:
- 獲取一段程序運(yùn)行時(shí)間的模板:
public abstract class GetTime{
public long getTime(){
long start = System.currentTimeMillis;
//表示要計(jì)算運(yùn)行時(shí)間的代碼
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);
}
}
}
- 測(cè)試:
public class test{
GetTime gt=new forDemo();
gt.getTime();
}
這樣就可以計(jì)算那個(gè)for循環(huán)運(yùn)行的時(shí)間了弄诲。
九、觀察者模式:
在對(duì)象之間定義了一對(duì)多的依賴娇唯,這樣一來齐遵,當(dāng)一個(gè)對(duì)象改變狀態(tài),依賴它的對(duì)象會(huì)收到通知并自動(dòng)更新塔插,這就是觀察者模式梗摇。
情景:有一個(gè)微信公眾號(hào)服務(wù),不定時(shí)發(fā)布一些消息想许,關(guān)注公眾號(hào)就可以收到推送消息伶授,取消關(guān)注就收不到推送消息。
示例代碼:
- 定義一個(gè)被觀察者接口:
public interface BeObserverd {
public void registerObserver(Observer o);// 添加觀察者
public void removeObserver(Observer o);// 刪除觀察者
public void notifyObserver();// 通知觀察者
}
- 定義一個(gè)觀察者接口:
public interface Observer {
public void update(String message);// 當(dāng)被觀察者發(fā)出通知時(shí)流纹,這個(gè)方法就會(huì)執(zhí)行
}
- 定義被觀察者糜烹,實(shí)現(xiàn)了BeObserverd接口,對(duì)BeObserverd接口的三個(gè)方法進(jìn)行了具體實(shí)現(xiàn)漱凝,同時(shí)有一個(gè)List集合疮蹦,用以保存注冊(cè)的觀察者,等需要通知觀察者時(shí)茸炒,遍歷該集合即可愕乎。
// 被觀察者,也就是微信公眾號(hào)服務(wù)實(shí)現(xiàn)了BeObserverd接口扣典,對(duì)BeObserverd接口的三個(gè)方法進(jìn)行了具體實(shí)現(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);
}
}
// 模擬公眾號(hào)推送消息的方法
public void setInfomation(String s) {
this.message = s;
System.out.println("微信服務(wù)更新消息: " + s);
//消息更新妆毕,通知所有觀察者
notifyObserver();
}
}
- 定義具體觀察者,微信公眾號(hào)的具體觀察者為用戶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)有三個(gè)人關(guān)注公眾號(hào)笛粘,當(dāng)微信公眾號(hào)調(diào)用setInfomation方法發(fā)送推文時(shí),通過調(diào)用notifyObserver方法,通知每個(gè)觀察者薪前,在notifyObserver方法里調(diào)用update方法润努,update里面調(diào)用read方法,三個(gè)人都能收到推送示括;加入現(xiàn)在有人取消關(guān)注了铺浇,那么就會(huì)調(diào)用removeObserver方法,下次推送這個(gè)人就收不到了垛膝。(本案例參考羅漢果的博文鳍侣,感謝大神整理!)
總結(jié):
本文聊了九個(gè)比較常用的設(shè)計(jì)模式吼拥,有些模式看文字可能覺得比較抽象難懂倚聚,通過案例代碼結(jié)合起來理解會(huì)更容易些。剩下的十四個(gè)設(shè)計(jì)模式凿可,下次再聊惑折。