一至非、問題的產(chǎn)生
舉例:鴨子模型
鴨子模型中會(huì)出現(xiàn)各種鴨子篷朵,會(huì)游泳勾怒,會(huì)呱呱叫婆排,于是設(shè)計(jì)一個(gè)鴨子超類(Duck)声旺,并讓各種鴨子繼承此超類一、問題的產(chǎn)生
舉例:鴨子模型
鴨子模型中會(huì)出現(xiàn)各種鴨子段只,會(huì)游泳腮猖,會(huì)呱呱叫,于是設(shè)計(jì)一個(gè)鴨子超類(Duck)赞枕,并讓各種鴨子繼承此超類
這時(shí)澈缺,如果想讓鴨子有會(huì)飛的特征,怎么辦呢炕婶?加上一個(gè)fly()方法姐赡,讓所有鴨子繼承fly()?
結(jié)果是所有的鴨子都會(huì)飛了柠掂,哪怕是不應(yīng)該會(huì)飛的橡皮鴨子项滑,另外橡皮鴨子也不是呱呱叫而是吱吱叫,這又怎么辦呢涯贞?采用覆蓋的方法枪狂,把quack()和fly()覆蓋成正確的方法危喉?
如果是誘餌鴨,是一只木頭假鴨州疾,既不會(huì)飛也不會(huì)叫怎么辦呢辜限?
如果有成千上萬種鴨子,都需要檢查哪些方法需要覆蓋嗎严蓖?
二薄嫡、解決辦法
有一個(gè)設(shè)計(jì)原則可以解決這些問題:找出應(yīng)用中可能需要變化之處,把它們獨(dú)立出來谈飒,不要和那些不需要變化的代碼混在一起岂座。也就是說把會(huì)變化的部分取出并封裝起來,以便以后可以輕易的改到或擴(kuò)充此部分杭措,而不影響不需要變化的其他部分费什。
鴨子模型中,fly()和quack()是需要經(jīng)常變化或修改的地方手素,把它們獨(dú)立出來鸳址,一個(gè)和fly相關(guān),一個(gè)和quack相關(guān)泉懦,并實(shí)現(xiàn)各自的動(dòng)作稿黍。
如何實(shí)現(xiàn)這兩個(gè)動(dòng)作呢?如果這兩種行為是在超類Duck的具體實(shí)現(xiàn)崩哩,或是一個(gè)接口巡球,由子類自行實(shí)現(xiàn),我們還是要在每個(gè)具體的鴨子類中實(shí)現(xiàn)它邓嘹。
另一個(gè)設(shè)計(jì)原則告訴我們:針對(duì)接口編程酣栈,而不是針對(duì)實(shí)現(xiàn)編程。制造一組行為類專門實(shí)現(xiàn)FlyBehavior和QuackBehavior汹押,由此行為類來實(shí)現(xiàn)行為接口矿筝,而不是Duck類。
最后鴨子模型的整體設(shè)計(jì)如下:
可以看出Duck類“HAS-A”(有一個(gè)→)接口實(shí)現(xiàn)<<interface>>FlayBehavior和<<interface>>QuackBehavior棚贾。
各種鴨子類型MallardDuck(綠頭鴨)窖维、ReadheadDuck(紅頭鴨)等“IS-A”(是一個(gè)
FlyWithWings妙痹、Quack等是implement(實(shí)現(xiàn)
“有一個(gè)HAS-A”的關(guān)系可以將各種行為(飛行,呱呱叫)委托他們處理怯伊。和“繼承”不同的地方在于琳轿,鴨子的行為不是繼承來的,而是適當(dāng)?shù)男袨椤敖M合”來的。
這遵循了一個(gè)設(shè)計(jì)原則:多用組合利赋,少用繼承水评。
這種組合的方法具有很大的彈性,不僅可將算法族封裝成類媚送,還可以“在運(yùn)行時(shí)動(dòng)態(tài)改變行為”中燥。
三、策略模式(Strategy Pattern)
1塘偎、定義
策略模式定義了算法族疗涉,分別封裝起來,讓他們直接可以互相替換吟秩,此模式讓算法的變化獨(dú)立于使用算法的客戶咱扣。在某一場(chǎng)景需要有多種情況,不同情況有不同的處理(大量 if-else 或者 switch)涵防,但大致功能是一樣的闹伪,這時(shí)我們可以考慮用策略模式實(shí)現(xiàn)。
2壮池、優(yōu)缺點(diǎn)
A偏瓤、優(yōu)點(diǎn):
1)每個(gè)算法都獨(dú)立于其他,方便單元測(cè)試椰憋。
2)結(jié)構(gòu)更加清晰厅克,不會(huì)有一堆條件語句處理不同情況。
3)客戶端引用的是接口橙依,耦合度低证舟,擴(kuò)展性強(qiáng)。
B窗骑、缺點(diǎn):
1)客戶端必須知道所有的策略類女责,并自行決定使用哪一個(gè)策略類。這就意味著客戶端必須理解這些算法的區(qū)別慧域,以便適時(shí)選擇恰當(dāng)?shù)乃惴惱鹬瘛Q言之浪读,策略模式只適用于客戶端知道所有的算法或行為的情況。
2)隨著策略增加,子類會(huì)增加耍鬓。
3碉钠、舉例
假設(shè)用戶分為普通用戶、會(huì)員痘拆、金牌會(huì)員仰禽,不同的用戶,購(gòu)買商品有不同的打折方式,一個(gè)用戶每消費(fèi)10000就增加一個(gè)級(jí)別吐葵,以上兩種用戶購(gòu)買產(chǎn)品花費(fèi)原價(jià)规揪、九折、八折温峭。
A猛铅、定義計(jì)算價(jià)格的策略接口
public interface CalPrice { //根據(jù)原價(jià)返回一個(gè)最終的價(jià)格
Double calPrice(Double orgnicPrice);
}
B、3種用戶購(gòu)買價(jià)格的計(jì)算方式的實(shí)現(xiàn)
public class Orgnic implements CalPrice {//普通用戶
@Override
public Double calPrice(Double orgnicPrice) {
return orgnicPrice;
}
}
public class Vip implements CalPrice { //會(huì)員
@Override
public Double calPrice(Double orgnicPrice) {
return orgnicPrice * 0.9;
}
}
public class SuperVip implements CalPrice { //金牌會(huì)員
@Override
public Double calPrice(Double orgnicPrice) {
return orgnicPrice * 0.8;
}
}
C凤藏、客戶類
public class Customer {
private Double totalAmount = 0;//客戶消費(fèi)的總額
private Double amount = 0;//客戶單次消費(fèi)金額
//每個(gè)客戶都有一個(gè)計(jì)算價(jià)格的策略奸忽,初始都是普通計(jì)算,即原價(jià)
private CalPrice calPrice = new Orgnic();
//客戶產(chǎn)品揖庄,就會(huì)增加它的總額
public void buy(Double amount) {
this.amount = amount;
totalAmount += amount;
if (totalAmount > 20000) {//改為金牌會(huì)員價(jià)格
calPrice = new SuperVip();
} else if (totalAmount > 10000) {//改為會(huì)員價(jià)格
calPrice = new Vip();
}
}
//計(jì)算客戶最終要付的錢
public Double calLastAmount() {
return calPrice.calPrice(amount);
}
}
D栗菜、客戶端調(diào)用,系統(tǒng)會(huì)幫我們自動(dòng)調(diào)整收費(fèi)策略蹄梢。
public class Client {
public static void main(String[] args) {
Customer customer = new Customer();
customer.buy(5000);
System.out.println("需要付錢:" + customer.calLastAmount());
customer.buy(12000);
System.out.println("需要付錢:" + customer.calLastAmount());
customer.buy(12000);
System.out.println("需要付錢:" + customer.calLastAmount());
}
}
客戶不再依賴于具體的收費(fèi)策略疙筹,依賴于抽象永遠(yuǎn)是正確的。
4禁炒、在android中的應(yīng)用
A腌歉、ListAdapter
如果ListView 要顯示的子布局是個(gè)簡(jiǎn)單的文字時(shí),我們可以使用 ArrayAdapter
mListView = (ListView) findViewById(R.id.list_view);
mListView.setAdapter(new ArrayAdapter<>(
this,
R.layout.item_text,
Arrays.asList("a", "b", "c")));
要顯示復(fù)雜些的布局時(shí)齐苛,需要用 BaseAdapter
mListView = (ListView) findViewById(R.id.list_view);
mListView.setAdapter(new BaseAdapter() {
@Override
public int getCount() {
return mData.size();
}
@Override
public object getItem(int positon) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = ...
return converView;
}
});
可以看到翘盖,當(dāng)更換Adapter的具體實(shí)現(xiàn)時(shí),仍然調(diào)用的是ListView.setAdapter(…)方法凹蜂,查看ListView源碼馍驯,發(fā)現(xiàn)setAdapter方法的參數(shù)是一個(gè) ListAdapter。
/**
* Sets the data behind this ListView.
*
* The adapter passed to this method may be wrapped by a {@Link WrapperList Adatper}
* depending ont the ListView features currently in use. For instance, adding
* headers and/or footers will cause the adapter to be wrapped.
*
* @param adapter the ListAdapter which is responsible for maintaining the
* data backing this list and for producing a view to represent an
* item in that data set.
*
* @see #getAdapter()
*/
@Overid
public void setAdapter(ListAdapter adapter) {
繼續(xù)看 ListAdapter 源碼和類結(jié)構(gòu)
可以看到ListAdapter是一個(gè)接口玛痊,ArrayAdapter和BaseAdapter是它的一個(gè)實(shí)現(xiàn)類汰瘫,在 ListView 中引用的是接口 ListAdapter,這就是一個(gè)策略模式的使用擂煞。
B混弥、時(shí)間插值器TimeInterpolator
時(shí)間插值器,它是一個(gè)接口对省,定義了動(dòng)畫改變的速率蝗拿,允許動(dòng)畫進(jìn)行非勻速變化。 在使用屬性動(dòng)畫時(shí)蒿涎,可以根據(jù)需要選擇合適的時(shí)間插值器
ObjectAnimator animator = ObjectAnimator.ofFloat(mListView, View.Alpha, 0f, 1f);
animator.setInterpolator(new AccelerateInterpolator()); //加速
animator.setInterpolator(new OvershootInterpolator()); //跑到頭又返回來
和ListView的setAdapter一樣哀托,ValueAnimator的setInterpolator方法中也引用的是接口TimeInterpolator
/**
* The time interpolator used in calculating the elapsed fraction of this animation. The
* interpolator determines whether the animation runs with linear or non_linear mation,
* such as acceleration and deceleration. The default value is
* {@link android.view.animation.AccelerateDecelerateInterpolator}
*
* @param value the interpolator to be used by this animation. A value of <code>null</code>
* will result in linear interpolation.
*/
@Overid
public void setInterpolator(TimeInterpolator value) {
if (value != null) {
mInterpolator = value;
} else {
mInterpolator = new LinearInterpolator();
}
}
TimeInterpolator 源碼及類結(jié)構(gòu)