Design Pattern:是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過(guò)分類編目的粟焊、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié),使用設(shè)計(jì)模式是為了可重用代碼孙蒙、讓代碼更容易被他人理解并且保證代碼可靠性项棠。
一.為什么要學(xué)習(xí)設(shè)計(jì)模式:
1.設(shè)計(jì)模式來(lái)源眾多專家的經(jīng)驗(yàn)和智慧,它們是從許多優(yōu)秀的軟件系統(tǒng)中總結(jié)出的成功的挎峦、能夠?qū)崿F(xiàn)可維護(hù)性沾乘、復(fù)用的設(shè)計(jì)方案,使用這些方案將可以讓我們避免做一些重復(fù)性的工作浑测。
2.設(shè)計(jì)模式提供了一套通用的設(shè)計(jì)詞匯和一種通用的形式來(lái)方便開(kāi)發(fā)人員之間溝通和交流,使得設(shè)計(jì)方案更加通俗易懂歪玲。
3.大部分設(shè)計(jì)模式都兼顧了系統(tǒng)的可重用性和可擴(kuò)展性迁央,這使得我們可以更好地重用一些已有的設(shè)計(jì)方案、功能模塊甚至一個(gè)完整的軟件系統(tǒng)滥崩,避免我們經(jīng)常做一些重復(fù)的設(shè)計(jì)岖圈、編寫(xiě)一些重復(fù)的代碼。
4.合理使用設(shè)計(jì)模式并對(duì)設(shè)計(jì)模式的使用情況進(jìn)行文檔化钙皮,將有助于別人更快地理解系統(tǒng)蜂科。
5.學(xué)習(xí)設(shè)計(jì)模式將有助于初學(xué)者更加深入地理解面向?qū)ο笏枷搿?/p>
二.六大原則
1.開(kāi)閉原則:一個(gè)軟件實(shí)體應(yīng)當(dāng)對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉短条。即軟件實(shí)體應(yīng)盡量在不修改原有代碼的情況下進(jìn)行擴(kuò)展导匣。為了滿足開(kāi)閉原則,需要對(duì)系統(tǒng)進(jìn)行抽象化設(shè)計(jì)茸时,抽象化是開(kāi)閉原則的關(guān)鍵
2.里氏替換原則:所有引用基類對(duì)象的地方能夠透明地使用其子類的對(duì)象
3.依賴倒置原則:抽象不應(yīng)該依賴于具體類贡定,具體類應(yīng)當(dāng)依賴于抽象。換言之可都,要針對(duì)接口編程缓待,而不是針對(duì)實(shí)現(xiàn)編程蚓耽。
4.單一職責(zé)原則:一個(gè)類只負(fù)責(zé)一個(gè)功能領(lǐng)域中的相應(yīng)職責(zé),或者可以定義為:就一個(gè)類而言旋炒,應(yīng)該只有一個(gè)引起它變化的原因步悠。是實(shí)現(xiàn)高內(nèi)聚、低耦合的指導(dǎo)方針瘫镇。
5.迪米特法則(最少知道原則):一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少地與其他實(shí)體發(fā)生相互作用鼎兽,在類的劃分上,應(yīng)當(dāng)盡量創(chuàng)建松耦合的類汇四,類之間的耦合度越低接奈,就越有利于復(fù)用。在類的結(jié)構(gòu)設(shè)計(jì)上通孽,每一個(gè)類都應(yīng)當(dāng)盡量降低其成員變量和成員函數(shù)的訪問(wèn)權(quán)限序宦。在類的設(shè)計(jì)上,只要有可能背苦,一個(gè)類型應(yīng)當(dāng)設(shè)計(jì)成不變類互捌。在對(duì)其他類的引用上,一個(gè)對(duì)象對(duì)其他對(duì)象的引用應(yīng)當(dāng)降到最低行剂。
6.接口分離原則:使用多個(gè)專門的接口秕噪,而不使用單一的總接口,即客戶端不應(yīng)該依賴那些它不需要的接口厚宰。
三.介紹幾種常用的設(shè)計(jì)模式
1.簡(jiǎn)單工廠模式
專門定義一個(gè)類(工廠類)來(lái)負(fù)責(zé)創(chuàng)建其他類的實(shí)例腌巾。可以根據(jù)創(chuàng)建方法的參數(shù)來(lái)返回不同類的實(shí)例铲觉,被創(chuàng)建的實(shí)例通常都具有共同的父類澈蝙。
首先我們看看API提供的Bitmap工廠
public class BitmapFactory {
private static final int DECODE_BUFFER_SIZE = 16 * 1024;
public static Bitmap decodeFile(String pathName, Options opts) {
validate(opts);
Bitmap bm = null;
InputStream stream = null;
try {
stream = new FileInputStream(pathName);
bm = decodeStream(stream, null, opts);
} catch (Exception e) {
/* do nothing.
If the exception happened on open, bm will be null.
*/
Log.e("BitmapFactory", "Unable to decode stream: " + e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// do nothing here
}
}
}
return bm;
}
public static Bitmap decodeFile(String pathName) {
return decodeFile(pathName, null);
}
@Nullable
public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
@Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
public static Bitmap decodeResource(Resources res, int id, Options opts) {
validate(opts);
Bitmap bm = null;
InputStream is = null;
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts);
} catch (Exception e) {
/* do nothing.
If the exception happened on open, bm will be null.
If it happened on close, bm is still valid.
*/
} finally {
try {
if (is != null) is.close();
} catch (IOException e) {
// Ignore
}
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
return bm;
}
現(xiàn)在我們來(lái)創(chuàng)建一個(gè)簡(jiǎn)單工廠
1.定義一個(gè)父類
public interface Model {
public void createModel();
}
2.定義兩個(gè)實(shí)例,實(shí)現(xiàn)父類方法
public class AndroidModel implements Model {
@Override
public void createModel() {
System.out.println("this is androidModel!");
}
}
public class IosModel implements Model {
@Override
public void createModel() {
System.out.println("this is iosModel!");
}
}
3.創(chuàng)建簡(jiǎn)單工廠
public class ModelFactory {
/**
* 單個(gè)方法
*/
public Model createModel(String type) {
if ("ios".equals(type)) {
return new IosModel();
} else if ("android".equals(type)) {
return new AndroidModel();
} else {
return null;
}
}
}
/**
* 多個(gè)方法
*/
public class ModelFactory {
private Model createAndroidModel() {
return new AndroidModel();
}
private Model createIosModel() {
return new IosModel();
}
}
/**
* 多個(gè)靜態(tài)方法
*/
public class ModelFactory {
private static Model createStaticAndroidModel() {
return new AndroidModel();
}
private static Model createStaticIosModel() {
return new IosModel();
}
}
2.工廠模式
簡(jiǎn)單工廠模式有一個(gè)問(wèn)題就是撵幽,類的創(chuàng)建依賴工廠類灯荧,也就是說(shuō),如果想要拓展程序盐杂,必須對(duì)工廠類進(jìn)行修改逗载,這違背了閉包原則,如何解決链烈?就用到工廠方法模式厉斟,創(chuàng)建一個(gè)工廠接口和創(chuàng)建多個(gè)工廠實(shí)現(xiàn)類,這樣一旦需要增加新的功能强衡,直接增加新的工廠類就可以了捏膨。
1.創(chuàng)建對(duì)應(yīng)的工廠
public interface ClientFactory {
public Model produce();
}
public class IosClientFactory implements ClientFactory{
@Override
public Model produce() {
return new IosModel();
}
}
public class AndroidClientFactory implements ClientFactory{
@Override
public Model produce() {
return new AndroidModel();
}
}
2.調(diào)用工廠方法
private void factoryTeat(){
ClientFactory factory=
new AndroidClientFactory();
Model model= factory.produce();
model.createModel();
}
3.單例模式
在一個(gè)進(jìn)程中保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。實(shí)現(xiàn)的方法為先判斷實(shí)例存在與否号涯,如果存在則直接返回目胡,如果不存在就創(chuàng)建了再返回,這就確保了一個(gè)類只有一個(gè)實(shí)例對(duì)象
一般單例我們分為懶漢式和餓漢式链快,懶漢式即為用到時(shí)再去給初始化單例誉己,餓漢式為程序啟動(dòng)時(shí)就初始化單例
/*懶漢式*/
public class SingleTest {
/* 持有私有靜態(tài)實(shí)例,防止被引用域蜗,此處賦值為null巨双,目的是實(shí)現(xiàn)延遲加載 */
private static SingleTest instance;
private SingleTest(){
}
public static SingleTest getInstance(){
if(instance==null){
instance=new SingleTest();
}
return instance;
}
}
/*餓漢式*/
public class SingleTest {
/* 持有私有靜態(tài)實(shí)例*/
private static SingleTest instance=new SingleTest();
private SingleTest(){
}
public static SingleTest getInstance(){
return instance;
}
}
雖然這兩種單例模式都能滿足我們對(duì)單例的需求,但是都會(huì)有一些問(wèn)題
餓漢式:在我們不用這個(gè)單例時(shí)霉祸,也進(jìn)行了初始化筑累,占用不必要的內(nèi)存資源
懶漢式:當(dāng)多個(gè)線程同時(shí)調(diào)用時(shí),會(huì)出現(xiàn)初始化多次的情況
基于兩種情況丝蹭,進(jìn)行以下優(yōu)化
1.方法加鎖
public static synchronized SingleTest getInstance2(){
if(instance==null){
instance=new SingleTest();
}
return instance;
}
但是synchronized關(guān)鍵字鎖住的是這個(gè)對(duì)象慢宗,這樣的用法,在性能上會(huì)有所下降奔穿,因?yàn)槊看握{(diào)用getInstance()镜沽,都要對(duì)對(duì)象上鎖。
2.代碼加鎖
public static SingleTest getInstance3(){
if(instance==null){
synchronized (instance){
if(instance==null){
instance=new SingleTest();
}
}
}
return instance;
}
在Java指令中創(chuàng)建對(duì)象和賦值操作是分開(kāi)進(jìn)行的贱田,也就是說(shuō)instance = new Singleton();語(yǔ)句是分兩步執(zhí)行的缅茉。但是JVM并不保證這兩個(gè)操作的先后順序,也就是說(shuō)有可能JVM會(huì)為新的Singleton實(shí)例分配空間男摧,然后直接賦值給instance成員蔬墩,然后再去初始化這個(gè)Singleton實(shí)例
SingleTest instance=new SingleTest()
new SingleTest() => instance
優(yōu)化方式
1.加volatile 字段
Volatile修飾的成員變量在每次被線程訪問(wèn)時(shí),都強(qiáng)迫從共享內(nèi)存中重讀該成員變量的值耗拓。而且拇颅,當(dāng)成員變量發(fā)生變化時(shí),強(qiáng)迫線程將變化值回寫(xiě)到共享內(nèi)存帆离。這樣在任何時(shí)刻,兩個(gè)不同的線程總是看到某個(gè)成員變量的同一個(gè)值结澄。
private volatile static SingleTest instance;
2.靜態(tài)內(nèi)部類 :JVM內(nèi)部的機(jī)制能夠保證當(dāng)一個(gè)類被加載的時(shí)候哥谷,這個(gè)類的加載過(guò)程是線程互斥的
public class SingleTest {
private static class SingleTestInstanceHolder{
private static final SingleTest singleInstance=new SingleTest();
}
public static SingleTest getInstance(){
return SingleTestInstanceHolder.singleInstance;
}
}
當(dāng)我們第一次調(diào)用getInstance的時(shí)候,JVM能夠幫我們保證instance只被創(chuàng)建一次麻献,并且會(huì)保證把賦值給instance的內(nèi)存初始化完畢
4.建造者模式
將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離们妥,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示,適用于初始化的對(duì)象比較復(fù)雜且參數(shù)較多的情況勉吻。
private void alertBuilder(Context context){
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setTitle("Title")
.setCancelable(false)
.setIcon(R.mipmap.ic_launcher)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
}
}).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
}
})
.setMessage("Message");
AlertDialog dialog = builder.create();
dialog.show();
}
public class AlertDialog extends Dialog implements DialogInterface {
public static class Builder {
private final AlertController.AlertParams P;
public Builder(Context context) {
this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
}
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
public AlertDialog create() {
/ Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
}
}
}
舉一個(gè)甜筒的列子package com.yuyin.live.room.entity:
房間公屏聊天內(nèi)容構(gòu)成
以前的寫(xiě)法
public class HnRoomPublicChatBean {
private int itemType;
private String userId;
private String userName;
private String userAvater;
public HnRoomPublicChatBean(int itemType, String userId) {
this.itemType = itemType;
this.userId = userId;
}
public HnRoomPublicChatBean(int itemType, String userId, String userName) {
this.itemType = itemType;
this.userId = userId;
this.userName = userName;
}
public HnRoomPublicChatBean(int itemType, String userId, String userName, String userAvater) {
this.itemType = itemType;
this.userId = userId;
this.userName = userName;
this.userAvater = userAvater;
}
}
優(yōu)化之后
public class HnRoomPublicChatBean {
/*
基礎(chǔ)參數(shù)
*/
private int itemType;
private String userId;
private String userName;
private String userAvater;
...
/**
構(gòu)造函數(shù)
*/
public HnRoomPublicChatBean(Builder builder){
this.itemType = builder.itemType;
this.userId = builder.userId;
this.userName = builder.userName;
this.userAvater = builder.userAvater;
...
}
/**
建造者
*/
public static class Builder{
private int itemType;
private String userId;
private String userName;
private String userAvater;
...
/**
建造
*/
public HnRoomPublicChatBean build() {
return new HnRoomPublicChatBean(this);
}
public Builder setUserId(String userId) {
this.userId = userId;
return this;
}
public Builder setUserNumber(String userNumber) {
this.userNumber = userNumber;
return this;
}
public Builder setUserName(String userName) {
this.userName = userName;
return this;
}
...
}
}
//#############使用
new HnRoomPublicChatBean.Builder()
.setItemType(HnRoomPublicChatBean.MSG_EGG_HAMMER)
.setUserId("用戶ID")
.setUserName("用戶昵稱")
.setUserAvater("用戶頭像")
...
.build()
5.觀察者模式
當(dāng)一個(gè)對(duì)象變化時(shí)监婶,其它依賴該對(duì)象的對(duì)象都會(huì)收到通知,并且隨著變化!
首先我們看看經(jīng)常用到的觀察者RecyclerViewDataObserver
MyAdapter adapter = new MyAdapter();
RecyclerView recyclerView=new RecyclerView(this)
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
我們先看看Adapter做了什么
public abstract static class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
public void onBindViewHolder(@NonNull VH holder, int position,
@NonNull List<Object> payloads) {
onBindViewHolder(holder, position);
}
@NonNull
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
try {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
if (holder.itemView.getParent() != null) {
throw new IllegalStateException("ViewHolder views must not be attached when"
+ " created. Ensure that you are not passing 'true' to the attachToRoot"
+ " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
}
holder.mItemViewType = viewType;
return holder;
} finally {
TraceCompat.endSection();
}
}
public final void bindViewHolder(@NonNull VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
holder.clearPayload();
final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof RecyclerView.LayoutParams) {
((LayoutParams) layoutParams).mInsetsDirty = true;
}
TraceCompat.endSection();
}
public final boolean hasObservers() {
return mObservable.hasObservers();
}
public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
mObservable.registerObserver(observer);
}
public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) {
mObservable.unregisterObserver(observer);
}
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
public final void notifyItemRangeChanged(int positionStart, int itemCount) {
mObservable.notifyItemRangeChanged(positionStart, itemCount);
}
}
然后我們看看RecyclerView以及setAdapter做了什么
···
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
public void setAdapter(@Nullable Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
requestLayout();
}
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
...
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
...
}
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
···
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
···
}
private class RecyclerViewDataObserver extends AdapterDataObserver {
···
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
···
}
那如果我們自己寫(xiě)一個(gè)觀察者應(yīng)該怎么做呢
創(chuàng)建一個(gè)觀察者(接口或者抽象類)
public interface Observer {
void update();
}
實(shí)現(xiàn)觀察者接口惑惶,處理數(shù)據(jù)變化之后的操作
public class ObserverOne implements Observer {
@Override
public void update() {
Log.e("observer1","update");
}
}
public class ObserverTwo implements Observer {
@Override
public void update() {
Log.e("observer2","update");
}
}
定義一個(gè)接口煮盼,用于注冊(cè)和解綁觀察者
public interface Users {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyDataSetChanged();
void updateData();
}
實(shí)現(xiàn)類
public class UsersObj implements Users {
private Vector<Observer> vector=new Vector();
@Override
public void addObserver(Observer observer) {
vector.add(observer);
}
@Override
public void removeObserver(Observer observer) {
vector.remove(observer);
}
@Override
public void notifyDataSetChanged() {
Enumeration<Observer> elements = vector.elements();
while (elements.hasMoreElements()){
elements.nextElement().update();
}
}
@Override
public void updateData() {
Log.e("updateData","user");
notifyDataSetChanged();
}
private void test(){
Users users=new ChinaUsers();
users.addObserver(new ObserverOne());
users.addObserver(new ObserverTwo());
users.notifyDataSetChanged();
}
}