前言
大一的時(shí)候?qū)W校就開了C語言這門課,最開始糊里糊涂無從下手,后來慢慢寫了幾個幾百行的小程序就逐漸明白編程是怎么一回事了,還以為自己都懂了(too young ?。?,可是后來蹭了一節(jié)java公選課,才知道還有面向?qū)ο缶幊踢@么一回事。老師說C是面向過程的,代碼超過十萬行就不好組織管理了,還得要面向?qū)ο蟮恼Z言才能解決這個問題。我當(dāng)時(shí)仿佛發(fā)現(xiàn)了一個新大陸,于是就開啟了自學(xué)java面向?qū)ο缶幊痰穆烦獭?/p>
面向?qū)ο笫鞘裁?/h1>
我感覺這個問題還真的不好回答,不同的人有不同的理解,下面就談?wù)勎业睦斫猓蠹抑刚?/p>
要說面向?qū)ο笫紫染偷谜f說面向過程(出現(xiàn)的先后順序),面向過程就是分析要解決的問題A,然后把這個問題劃分為不同的步驟A1,A2,...(或者問題足夠小就不用劃分)。然后用函數(shù)把這些子問題一個一個地實(shí)現(xiàn),最終解決A。
而面向?qū)ο笫前褑栴}中出現(xiàn)的角色獨(dú)立出來,讓他們互相通信來完成最終的問題。
不管是面向過程還是面向?qū)ο螅际俏覀冋J(rèn)識世界的一種方法。那你可能會問了,既然面向過程先出現(xiàn)而且能解決問題,那么面向?qū)ο鬄槭裁磿霈F(xiàn)呢?首先面向過程既然出現(xiàn)就肯定有它的道理,因?yàn)樗衔覀冏畛R姷倪壿?,我們現(xiàn)實(shí)生活中遇上問題大多會采用這種思維,比如我有個目標(biāo)A:“我要成為科學(xué)家” ,那么很自然我就會想到A1:完成小學(xué),A2:完成中學(xué),A3:完成大學(xué),A4:完成研究生,...,An:獲得科研機(jī)構(gòu)認(rèn)可。把這一系列子問題挨個解決,那么我的問題就解決了,如圖:
代碼:
A(baby){
primary = A1(baby);
middle = A2(primary);
university = A3(middle);
graduate = A4(university);
//...
scientist = An(graduate);
return scientist;
}
這是一種很自然的思路,所以面向過程最早出現(xiàn)。但是這種思路有個問題就是當(dāng)系統(tǒng)龐大了龐大起來過后,各個步驟之間協(xié)調(diào)起來比較復(fù)雜,修改一個步驟可能引起很多的步驟的改變,比如那一天中國在中學(xué)和大學(xué)之間增加了一個學(xué)歷preUniversity,那么至少就得改動兩個函數(shù),如果美國沒有小學(xué)那么這個函數(shù)就得搞一個美國版去掉A1,修改A2。等等等等。
這既不利于維護(hù),也沒有達(dá)到代碼可重用的目的。面向?qū)ο缶蛻?yīng)運(yùn)而生了,用面向?qū)ο蟮乃悸肪涂梢匀缦陆鉀Q:
代碼:
//人
class People(){
}
//教育機(jī)構(gòu)
class Education(People people){
private People people = people;
public void primary(People people);
public void middle (People people);
public void university (People people);
public void graduate (People people);
//...
public void scientist (People people);
public bool finish(){
primary(people);
middle (people);
university (people);
graduate (people);
scientist (people);
retrun true;
}
}
//科研機(jī)構(gòu)
class Institution(){
public void Recognize(Education education){
if(education.finish()&&others){//完成學(xué)業(yè)以及科研機(jī)構(gòu)的其他判斷條件
print "Scientist";
}else{
print "Not Scientist";
}
}
public static void main(Sting args[]){
People people = new People();
Education education = new Education(people);
Recognize(education);
}
}
這樣的話,即使學(xué)習(xí)歷程有變化都可以只在Education類中修改,不同國別學(xué)歷不同也可以繼承這個基類來實(shí)現(xiàn),大大提高了維護(hù)性和重用性。
設(shè)計(jì)模式是什么
概念上來說,如果一個問題反復(fù)發(fā)生,那么這個問題的解決方案就會被反復(fù)的使用,這種被頻繁使用的解決方案就是模式。設(shè)計(jì)模式是語言獨(dú)立的,主要用來解決程序設(shè)計(jì)的一般性問題(簡單說來就是組織代碼)。比如我們做菜,無論是西紅柿炒雞蛋還是紅燒排骨,做多了就發(fā)現(xiàn)這個行為是有著固定模式的:點(diǎn)火,切菜,入鍋,裝進(jìn)盤子。于是乎炒菜便成為了一種模式,以后每種菜(菜品獨(dú)立)都可以沿襲這種模式,然后不同的菜實(shí)施細(xì)節(jié)不同而已。
按我理解,設(shè)計(jì)模式存在的終極意義就是代碼的可重用性(也就順帶了可維護(hù)性)。其實(shí)不只是設(shè)計(jì)模式,面向過程和面向?qū)ο蟊旧砥鋵?shí)也有這個作用,只不過面向過程是通過函數(shù)來實(shí)現(xiàn),而面向?qū)ο笫峭ㄟ^類來實(shí)現(xiàn),而且面向?qū)ο蟀堰@個目的完成的更好一些。更徹底一些。
設(shè)計(jì)模式一般來說包含如下幾個方面:
- 模式名稱,設(shè)計(jì)模式的名稱是重要的,因?yàn)樗鼤屍渌绦騿T能立刻理解你的代碼的目的
- 問題陳述,問題描述是用來說明這個模式的應(yīng)用的領(lǐng)域,即應(yīng)該在何時(shí)使用該模式
- 解決方案,解決方案描述了這個模型的執(zhí)行
- 效果,描述了模式應(yīng)用的效果及使用模式應(yīng)權(quán)衡的問題
下面我就通過一些常見的設(shè)計(jì)模式來說明設(shè)計(jì)模式是怎樣起到這種效果的。
工廠方法模式
問題陳述
在面向?qū)ο缶幊讨? 最通常的方法是一個 new 操作符產(chǎn)生一個對象實(shí)例,new 操作符就是用來構(gòu)造對象實(shí)例的。但是在一些情況下 , new操作符直接生成對象會帶來一些問題。舉例來說, 許多類型對象的創(chuàng)造需要一系列的步驟: 你可能需要計(jì)算或取得對象的初始設(shè)置 ;選擇生成哪個子對象實(shí)例 ; 或在生成你需要的對象之前必須先生成一些輔助功能的對象。在這些情況, 新對象的建立就是一個 “過程” ,不僅是一個操作,像一部大機(jī)器中的一個齒輪傳動。你如何能輕松方便地建立這么" 復(fù)雜 " 的對象即操作中不需要粘貼復(fù)制呢?
解決方案
定義一個用于創(chuàng)建對象的接口,讓子類決定實(shí)例化哪一個類。工廠方法使一個類的實(shí)例化延遲到其子類。
工廠方法模式通用類圖:
在工廠方法模式中,抽象產(chǎn)品類Product負(fù)責(zé)定義產(chǎn)品的共性,實(shí)現(xiàn)對事物最抽象的定義;Creator為抽象創(chuàng)建類,也就是抽象工廠,具體如何創(chuàng)建產(chǎn)品類是由具體的實(shí)現(xiàn)工廠ConcreteCreator完成的。我們來看一個比較實(shí)用的通用源碼:
//抽象產(chǎn)品類
public abstract class Product {
//產(chǎn)品類的公共方法
public void method1(){
//業(yè)務(wù)邏輯處理
}
//抽象方法
public abstract void method2();
}
//具體產(chǎn)品類,可以有多個
public class ConcreteProduct1 extends Product {
public void method2() {
//業(yè)務(wù)邏輯處理
}
}
public class ConcreteProduct2 extends Product {
public void method2() {
//業(yè)務(wù)邏輯處理
}
}
//抽象工廠類
public abstract class Creator {
//創(chuàng)建一個產(chǎn)品對象,其輸入?yún)?shù)類型可以自行設(shè)置
//通常為String、Enum、Class等,當(dāng)然也可以為空
public abstract <T extends Product> T createProduct(Class<T> c);
}
//具體工廠類
public class ConcreteCreator extends Creator {
public <T extends Product> T createProduct(Class<T> c){
Product product=null;
try {
product = (Product)Class.forName(c.getName()).newInstance();
} catch (Exception e) {
//異常處理
}
return (T)product;
}
}
//場景類
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreator();
Product product = creator.createProduct(ConcreteProduct1.class);
//繼續(xù)業(yè)務(wù)處理
}
}
效果
首先,代碼具有了良好的封裝性,結(jié)構(gòu)清晰。一個對象創(chuàng)建是有條件約束的,一個調(diào)用者需要一個具體的產(chǎn)品對象,只要知道這個產(chǎn)品的類名(或約束字符串)就可以了,不用知道創(chuàng)建對象的艱辛過程,降低模塊間的耦合。
其次,工廠方法模式的擴(kuò)展性非常好。在增加產(chǎn)品類的情況下,只要適當(dāng)?shù)匦薷木唧w的工廠類或擴(kuò)展一個工廠類就行了。
再次,屏蔽產(chǎn)品類。這一特點(diǎn)非常重要,產(chǎn)品類的實(shí)現(xiàn)如何變化,調(diào)用者都不需要關(guān)心,它只需要關(guān)心產(chǎn)品的接口,只要接口保持不變,系統(tǒng)中的上層模塊就不要發(fā)生變化。因?yàn)楫a(chǎn)品類的實(shí)例化工作是由工廠類負(fù)責(zé)的,一個產(chǎn)品對象具體由哪一個產(chǎn)品生成是由工廠類決定的。在數(shù)據(jù)庫開發(fā)中,大家應(yīng)該能夠深刻體會到工廠方法模式的好處:如果使用JDBC連接數(shù)據(jù)庫,數(shù)據(jù)庫從My SQL 切換到Oracle,需要改動的地方就是切換一下驅(qū)動名稱(前提
條件是SQL 語句是標(biāo)準(zhǔn)語句),其他的都不需要修改,這是工廠方法模式靈活性的一個直接案例。
最后,工廠方法模式是典型的解耦框架。
觀察者模式
問題陳述
如果一個事件A發(fā)生了需要觸發(fā)另一個事件B之時(shí)該怎么辦呢?直接修改A?顯然不是科學(xué)的程序設(shè)計(jì),因?yàn)檫@樣就達(dá)不到A,B解耦合的目的,如果要修改B就得直接修改A;如果A同時(shí)還要觸發(fā)事件C怎么辦?又繼續(xù)往A里面添加?顯然這樣做最后會導(dǎo)致A臃腫不堪難以維護(hù),這個時(shí)候就需要用到觀察者模式了。
解決方案
定義對象間一種一對多的依賴關(guān)系,使得每當(dāng)一個對象改變狀態(tài),則所有依賴于它的對象都會得到通知并被自動更新。
Subject被觀察者,定義被觀察者必須實(shí)現(xiàn)的職責(zé),它必須能夠動態(tài)地增加、取消觀察者。它一般是抽象類或者是實(shí)現(xiàn)類,僅僅完成作為被觀察者必須實(shí)現(xiàn)的職責(zé):管理觀察者并通知觀察者。
Observer觀察者,觀察者接收到消息后,即進(jìn)行update(更新方法)操作,對接收到的信息進(jìn)行處理。
ConcreteSubject具體的被觀察者,定義被觀察者自己的業(yè)務(wù)邏輯,同時(shí)定義對哪些事件進(jìn)行通知。
ConcreteObserver具體的觀察者,每個觀察在接收到消息后的處理反應(yīng)是不同,各個觀察者有自己的處理邏輯。
通用代碼如下:
//被觀察者
public abstract class Subject {
//定義一個觀察者數(shù)組
private Vector<Observer> obsVector = new Vector<Observer>();
//增加一個觀察者
public void addObserver(Observer o){
this.obsVector.add(o);
}
//刪除一個觀察者
public void delObserver(Observer o){
this.obsVector.remove(o);
}
//通知所有觀察者
public void notifyObservers(){
for(Observer o:this.obsVector){
o.update();
}
}
}
//具體被觀察者
public class ConcreteSubject extends Subject {
//具體的業(yè)務(wù)
public void doSomething(){
//do something
super.notifyObservers();
}
}
//觀察者
public interface Observer {
//更新方法
public void update();
}
//具體觀察者
public class ConcreteObserver implements Observer {
//實(shí)現(xiàn)更新方法
public void update() {
System.out.println("接收到信息,并進(jìn)行處理!");
}
}
//場景類
public class Client {
public static void main(String[] args) {
//創(chuàng)建一個被觀察者
ConcreteSubject subject = new ConcreteSubject();
//定義一個觀察者
Observer obs= new ConcreteObserver();
//觀察者觀察被觀察者
subject.addObserver(obs);
//觀察者開始活動了
subject.doSomething();
}
}
效果
首先,觀察者和被觀察者之間是抽象耦合。如此設(shè)計(jì),則不管是增加觀察者還是被觀察者都非常容易擴(kuò)展,而且在Java中都已經(jīng)實(shí)現(xiàn)的抽象層級的定義,在系統(tǒng)擴(kuò)展方面更是得心應(yīng)手。
其次,建立了一套觸發(fā)機(jī)制根據(jù)單一職責(zé)原則,每個類的職責(zé)是單一的,那么怎么把各個單一的職責(zé)串聯(lián)成真實(shí)世
界的復(fù)雜的邏輯關(guān)系呢?比如,我們?nèi)ゴ颢C,打死了一只母鹿,母鹿有三個幼崽,因失去了母鹿而餓死,尸體又被兩只禿鷹爭搶,因分配不均,禿鷹開始斗毆,然后羸弱的禿鷹死掉,生存下來的禿鷹,則因此擴(kuò)大了地盤……這就是一個觸發(fā)機(jī)制,形成了一個觸發(fā)鏈。觀察者模式可以完美地實(shí)現(xiàn)這里的鏈條形式。
策略模式
問題陳述
如果你有很多的算法,他們只是有些許不同,你需要在不同場景使用不同的算法,這個時(shí)候是不是需要很多的if語句來判斷呢?可是if語句寫多了又不好維護(hù);
如果你的算法規(guī)則不希望被別人看見,該怎么辦;
這一切都交給策略模式吧。
解決方案
定義一組算法,將每個算法都封裝起來,并且使它們之間可以互換。
策略模式的通用類圖:
Context 封裝角色,它也叫做上下文角色,起承上啟下封裝作用,屏蔽高層模塊對策略、算法的直接訪問,封裝可能存在的變化。
Strategy 抽象策略角色,策略、算法家族的抽象,通常為接口,定義每個策略或算法必須具有的方法和屬性。
ConcreteStrategy 具體策略角色,實(shí)現(xiàn)抽象策略中的操作,該類含有具體的算法。
通用代碼:
//抽象的策略角色
public interface Strategy {
//策略模式的運(yùn)算法則
public void doSomething();
}
//具體策略角色
public class ConcreteStrategy1 implements Strategy {
public void doSomething() {
System.out.println("具體策略1的運(yùn)算法則");
}
}
public class ConcreteStrategy2 implements Strategy {
public void doSomething() {
System.out.println("具體策略2的運(yùn)算法則");
}
}
//封裝角色
public class Context {
//抽象策略
private Strategy strategy = null;
//構(gòu)造函數(shù)設(shè)置具體策略
public Context(Strategy _strategy){
this.strategy = _strategy;
}
//封裝后的策略方法
public void doAnythinig(){
this.strategy.doSomething();
}
}
//場景類
public class Client {
public static void main(String[] args) {
//聲明一個具體的策略
Strategy strategy = new ConcreteStrategy1();
//聲明上下文對象
Context context = new Context(strategy);
//執(zhí)行封裝后的方法
context.doAnythinig();
}
}
效果
首先,算法可以自由切換。這是策略模式本身定義的,只要實(shí)現(xiàn)抽象策略,它就成為策略家族的一個成員,通過封
裝角色對其進(jìn)行封裝,保證對外提供“可自由切換”的策略。
其次,避免使用多重條件判斷。如果沒有策略模式,我們想想看會是什么樣子?一個策略家族有5個策略算法,一會要
使用A策略,一會要使用B策略,怎么設(shè)計(jì)呢?使用多重的條件語句?多重條件語句不易維護(hù),而且出錯的概率大大增強(qiáng)。使用策略模式后,可以由其他模塊決定采用何種策略,策略家族對外提供的訪問接口就是封裝類,簡化了操作,同時(shí)避免了條件語句判斷。
最后,擴(kuò)展性良好。在現(xiàn)有的系統(tǒng)中增加一個策略太容易了,只要實(shí)現(xiàn)接口就可以了,其他都不用修改,類似于一個可反復(fù)拆卸的插件,這大大地符合了OCP原則。
總結(jié)
還有很多設(shè)計(jì)模式我就不一一闡述了。大家可以找找資料看看。
我感覺設(shè)計(jì)模式說白了就是對面向?qū)ο缶幊痰囊环N補(bǔ)充,把面向?qū)ο蠓椒ㄋM_(dá)到的效果進(jìn)一步完善罷了。但是這些模式還得在實(shí)戰(zhàn)中慢慢體會,以后編程過程中慢慢加深運(yùn)用設(shè)計(jì)模式,相信會對我們寫出可復(fù)用、易維護(hù)的代碼有幫助的。
本文參考了《大話設(shè)計(jì)模式》,這本書通俗易懂,風(fēng)趣幽默,強(qiáng)烈推薦給大家~
歡迎訪問我的主頁(http://mageek.cn)