# 設計模式 Design Patterns --- ## 前言 設計模式被人誤解的一個重要原因是人們對它的誤用和濫用,比如將一些模式用在了錯誤的場景中,或者說在不該使用模式的地方刻意使用模式。特別是初學者在剛學會使用一個模式時,恨不得把所有的代碼都用這個模式來實現,模式應該用在正確的地方。而哪些才算正確的地方,只有在我們深刻理解了模式的意圖之後,再結合項目的實際場景才會知道。 分辨模式的關鍵是意圖而不是結構,許多模式的類圖看起來都差不多,模式只有放在具體的環境下才有意義,辨別模式的關鍵是這個模式出現的場景,以及為我們解決了什麼問題。 --- ## 策略模式 [策略模式投影片連結](https://docs.google.com/presentation/d/1SrXAzAQctNB5k_mfQe65GVXNP2IpjPfcuFsGAmUBPhM/edit#slide=id.p) 定義了演算法家族,彼此封裝起來,讓它們之間可以互相替換,此模式讓演算法的變動不會影響到使用者的程式。 VS樣板方法模式 樣板方法模式定義完整的演算法,將部分細部方法實作交由次類別決定,有時會使用合成的方式。 策略模式則是用合成的方式,演算法全部都在合成物件裡。 VS狀態模式 策略模式的演算法變動通常是在有目的有意識的情形下。 狀態模飾則是由演算法決定,使用者甚至不會意識到演算法的改變。 - 鴨子亂改版 .Net C# | 傳統Oop 策略模式 | 混搭Fp (Functional Programing) 的策略模式 | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | - 首先,這不是Java ,這不是Java,這不是Java<br> - 模仿很好,但不要忘記這是 .Net<br> - 善用 .Net 優勢 與 撰寫慣例 簡化程式碼<br>- .Net 中的介面慣用命名方式 為大寫 I +名稱, 例如 : 飛行介面 IFly<br>- 簡化 飛行 & 叫聲 行為<br> - 改為屬性 get set 一次寫完<br> - set 改為 protected<br>- 鴨子抽象類別建構子改為 必須遞 飛行 &叫聲行為進去, 並增加鴨子介面 IDuck | - 首先,這不是Java ,這不是Java,這不是Java<br> - 善用 .Net 優勢 <br> - .Net 除了介面,還有 delegate(委派)可以使用<br> - 適合簡化於不複查的簡單介面,例如:只有一個方法的介面的 IFly IQuack<br> - 複雜的介面不適合使用,例如:鴨子介面 IDuck<br>- 同左,但以 Action 取代 介面 IFly IQuack<br>- 附註 <br> - Action 為 .Net 內建的 delegate(委派)<br> - 若為 .net framework 2.0 則需要自行擴充 <br> - public delegate void Action(); | | 鴨子抽象類別 | 鴨子抽象類別 | | public abstract class Duck : IDuck<br>{<br> public Duck(IFly fly, IQuack quack)<br> {<br> Fly = fly;<br> Quack = quack;<br> }<br> public IFly Fly { get; protected set; }<br> public IQuack Quack { get; protected set; }<br> public void Swim()<br> {<br> Console.WriteLine("All duck s float, even decoys");<br> }<br> public abstract void Display();<br>}<br>public interface IDuck<br>{<br> void Swim();<br> IFly Fly { get; }<br> IQuack Quack { get;}<br> void Display();<br>} | public abstract class Duck : IDuck<br>{<br> public Duck(Action fly, Action quack)<br> {<br> Fly = fly;<br> Quack = quack;<br> }<br> public Action Fly { get; protected set; }<br> public Action Quack { get; protected set; }<br> public void Swim()<br> {<br> Console.WriteLine("All duck s float, even decoys");<br> }<br> public abstract void Display();<br>}<br>public interface IDuck<br>{<br> void Swim();<br> Action Fly { get; }<br> Action Quack { get;<br> void Display();<br>} | | 飛行行為<br><br> - 介面 與 實作 | 飛行行為<br><br> - 其實是不用實作,在此建立 FlyFactory 類別,存放一些常用的實作 | | public interface IFly<br>{<br> void fly();<br>}<br>public class FlyNoWay : IFly<br>{<br> public void fly()<br> {<br> Console.WriteLine("I can't fly");<br> }<br>}<br>public class FlyWithWings : IFly<br>{<br> public void fly()<br> {<br> Console.WriteLine("I'm flying with my wings");<br> }<br>} | public static class FlyFactory<br>{<br> public static void FlyNoWay()<br> {<br> Console.WriteLine("I can't fly");<br> }<br> public static void FlyWithWings()<br> {<br> Console.WriteLine("I'm flying with my wings");<br> }<br>} | | 叫聲行為<br><br> - 介面 與 實作 | 叫聲行為<br><br> - 其實是不用實作,在此建立 QuackFactory 類別,存放一些常用的實作 | | public interface IQuack<br>{<br> void quack();<br>}<br>public class MuteQuack : IQuack<br>{<br> public void quack()<br> {<br> Console.WriteLine("I say ...(Silence)");<br> }<br>}<br>public class Quack : IQuack<br>{<br> public void quack()<br> {<br> Console.WriteLine("I say Quack");<br> }<br>}<br>public class Squeak : IQuack<br>{<br> public void quack()<br> {<br> Console.WriteLine("I say Squeak");<br> }<br>} | public static class QuackFactory<br> {<br> public static void MuteQuack()<br> {<br> Console.WriteLine("I say ...(Silence)");<br> }<br> public static void Quack()<br> {<br> Console.WriteLine("I say Quack");<br> }<br> public static void Squeak()<br> {<br> Console.WriteLine("I say Squeak");<br> }<br> } | | 子類別鴨子實作 | 子類別鴨子實作 | | public class DecoyDuck : Duck, IDuck<br>{<br> public DecoyDuck()<br> : base(new FlyNoWay(), new MuteQuack())<br> {<br> } <br> public override void Display()<br> {<br> Console.WriteLine("I'm a real decoy duck");<br> }<br>}<br>public class MallardDuck : Duck<br>{<br> public MallardDuck() <br> : base(new FlyWithWings(), new Quack())<br> {<br> }<br> public override void Display()<br> {<br> Console.WriteLine("I'm a real mallard duck");<br> }<br>}<br>public class RedheadDuck : Duck<br> {<br> public RedheadDuck() <br> : base(new FlyWithWings(), new Quack())<br> {<br> }<br> public override void Display()<br> {<br> Console.WriteLine("I'm a real redhead duck");<br> }<br> }<br> public class RubberDuck : Duck<br>{<br> public RubberDuck()<br> : base(new FlyNoWay(), new Squeak())<br> {<br> }<br> public override void Display()<br> {<br> Console.WriteLine("I'm a real rubber duck");<br> }<br>} | public class DecoyDuck : Duck, IDuck<br>{<br> public DecoyDuck() <br> : base(FlyFactory.FlyNoWay, QuackFactory.MuteQuack)<br> {<br> } <br> public override void Display()<br> {<br> Console.WriteLine("I'm a real decoy duck");<br> }<br>}<br>public class MallardDuck : Duck<br> {<br> public MallardDuck()<br> : base(FlyFactory.FlyWithWings, QuackFactory.Quack)<br> {<br> }<br> public override void Display()<br> {<br> Console.WriteLine("I'm a real mallard duck");<br> }<br> }<br>public class RedheadDuck : Duck<br>{<br> public RedheadDuck() <br> : base(FlyFactory.FlyWithWings, QuackFactory.Quack)<br> {<br> }<br> public override void Display()<br> {<br> Console.WriteLine("I'm a real redhead duck");<br> }<br>}<br>public class RubberDuck : Duck<br>{<br> public RubberDuck()<br> : base(FlyFactory.FlyNoWay, QuackFactory.Squeak)<br> {<br> }<br> public override void Display()<br> {<br> Console.WriteLine("I'm a real rubber duck");<br> }<br>} | | 便於日後擴充用的模型鴨實作 | 便於日後擴充用的模型鴨實作 | | public class ModelDuck : Duck<br>{<br> public ModelDuck(IFly fly, IQuack quack) <br> : base(fly, quack)<br> {<br> }<br> public override void Display()<br> {<br> Console.WriteLine("I'm a real model duck");<br> }<br>} | public class ModelDuck : Duck<br>{<br> public ModelDuck(Action fly, Action quack) <br> : base(fly, quack)<br> {<br> }<br> public override void Display()<br> {<br> Console.WriteLine("I'm a real model duck");<br> }<br>} | | Sample 測試 | Sample 測試 | | Console.WriteLine("=== StrategySample.Oop ====");<br>List<IDuck> duckList = new List<IDuck>();<br>duckList.Add(new DecoyDuck());<br>duckList.Add(new MallardDuck());<br>duckList.Add(new RedheadDuck());<br>duckList.Add(new RubberDuck());<br>//模型鴨測式 既有已知行為<br>duckList.Add(new ModelDuck(new FlyNoWay(), new Squeak()));<br>//模型鴨測式 未知的行為 : 需實作新的類別 在此不示範<br><br>foreach (var duck in duckList)<br>{<br> duck.Display();<br> duck.Fly.fly();<br> duck.Quack.quack();<br>} | Console.WriteLine("=== StrategySample.OopAndFp ====");<br>List<IDuck> duckList = new List<IDuck>();<br>duckList.Add(new DecoyDuck());<br>duckList.Add(new MallardDuck());<br>duckList.Add(new RedheadDuck());<br>duckList.Add(new RubberDuck());<br>//模型鴨測式 既有已知行為<br>duckList.Add(new ModelDuck(FlyFactory.FlyNoWay, QuackFactory.Squeak));<br>//模型鴨測式 未知的行為 : 不需實作新的類別<br>duckList.Add(new ModelDuck(<br> () => Console.WriteLine("try Fly Method"),<br> () => Console.WriteLine("try Quack Method")));<br>foreach (var duck in duckList)<br>{<br> duck.Display();<br> duck.Fly();<br> duck.Quack(); <br>} | ![](https://i.imgur.com/qnRKeeG.png) - 鴨子亂改版 Python 3 ![](https://i.imgur.com/MVKoAh1.png) ![](https://i.imgur.com/xxZtR5Z.png) --- ## 觀察者模式 觀察者模式投影片連結 在物件之間定義一對多的關係;如此一來,當一個物件的狀態改變,其關係物件都會收到通知,並自動更新。 - 達輝壽星快報 .Net C# ![](https://i.imgur.com/7mqApAU.png) - 氣象站改版 .Net C# - 使用 delegate & event  ``` public interface ISubject<TData> { // 訂閱 void RegisterObserver(IObserver<TData> observer); // 取消訂閱 void RemoveObserver(IObserver<TData> observer); // 狀態變化時,通知所有觀察者 void NotifyObservers(); } public interface IObserver<TData> { // 氣象之變化時,subject會把這些值更新給observers void Update(TData data); } public interface IDisplayElement { // 布告板顯示 void Display(); } public delegate void NotifyHandler(ModelData data); public class ModelData { public float Temp { get; set; } public float Humidity { get; set; } public float Pressure { get; set; } } public class WeatherData : ISubject<ModelData> { private ModelData _data = new ModelData(); private event NotifyHandler NotifyEvent; public WeatherData() { } public void NotifyObservers() { NotifyEvent(_data); } public void MeasurementsChanged() { NotifyObservers(); } public void SetMeasurements(float temp, float humidity, float pressure) { _data.Temp = temp; _data.Humidity = humidity; _data.Pressure = pressure; MeasurementsChanged(); } public void RegisterObserver(IObserver<ModelData> observer) { NotifyEvent += observer.Update; } public void RemoveObserver(IObserver<ModelData> observer) { NotifyEvent -= observer.Update; } } public class CurrentConditionDisplay : IObserver<ModelData>, IDisplayElement { private float _temp, _humidity; public CurrentConditionDisplay(ISubject<ModelData> weatherData) { weatherData.RegisterObserver(this); } public void Update(ModelData data) { _temp = data.Temp; _humidity = data.Humidity; Display(); } // 這個布告板只顯示溫度和濕度 public void Display() { Console.WriteLine($"Current conditions:{_temp}℃ and {_humidity}% humidity"); } } WeatherData weatherData = new WeatherData(); CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData); weatherData.SetMeasurements(10, 20, 30); weatherData.SetMeasurements(14, 25, 36); weatherData.SetMeasurements(40, 50, 60); ``` ## 裝飾者模式 動態的將責任加諸於物件上。想要擴充功能,裝試者提供有別於繼承另一個選擇。 VS轉接器模式 轉接器模式會變動介面,但裝飾者絕對不會。 轉接器模式是將不同介面類似行為的物件,轉接成相同物件。 裝飾者模式則是在相同介面下,在方法加上新的行為和義務。 VS代理人模式 裝飾者模式由一個物件包裝另一個物件,會包裝很多層。 代理人代表一個物件,有部分情形會使用包裝的方式,但不會有包裝多層的情形。 - .Net C# 亂改版 (自己裝飾自己) // 點了一杯 Espresso, 印出它的資訊 Beverage beverage = new Espresso(); Console.WriteLine($"{beverage.Description} ${beverage.Cost}"); // 再點一杯 Espresso Beverage beverage2 = new Espresso(); // 加料, 改用 Append 方法 , 讓程式碼更易讀 beverage2 = beverage2.Append(new Mocha()) .Append(new Milk()); Console.WriteLine($"{beverage2.Description} ${beverage2.Cost}"); // 誰說覆水難收, 還原大法 beverage2 = beverage2.Rollback(); Console.WriteLine($"{beverage2.Description} ${beverage2.Cost}"); ![](https://i.imgur.com/2TtXcWw.png) public class Beverage { protected Beverage beverage; public Beverage(){} protected Beverage(Beverage beverage, params Beverage[] beverages) { this.beverage = beverage; this.Description = $"{beverage.Description}, {string.Join(", ", beverages.Select(m => m.Description))}"; this.Cost = beverage.Cost + beverages.Sum(m => m.Cost); } public string Description { get; protected set; } = "Unknown Beverage"; public double Cost { get; protected set; } = 0; public Beverage Append(params Beverage[] adds) { return new Beverage(this, adds); } public Beverage Rollback() { return this.beverage; } } public class Espresso : Beverage { public Espresso() { Description = "Espresso"; Cost = 1.99; } } public class Mocha : Beverage { public Mocha() { Description = "Mocha"; Cost = .20; } } public class Milk : Beverage { public Milk() { Description = "Milk"; Cost = .30; } } --- ## 工廠模式 Q&A 1. 抽象工廠 v.s. 工廠方法 2. 何時該使用抽象工廠?何時該使用工廠方法? 補充資料: 1. 關於 “工廠模式” 與 “抽象工廠模式“ 兩個模式差異的討論 http://stackoverflow.com/questions/5739611/differences-between-abstract-factory-pattern-and-factory-method 2. 工廠模式介紹 https://www.youtube.com/watch?v=ub0DXaeV6hA 3. 抽象工廠模式介紹 https://www.youtube.com/watch?v=xbjAsdAK4xQ 4. JavaScript 工廠模式 1. 三種工廠模式 2. JavaScript設計模式與開發實踐 前言:JavaScript 的物件是多態性,不存在類型偶合的問題,實際上是不需要工廠方法模式.模式的存在首先是能為我們解決什麼問題,牽強的模擬只會讓人覺得設計模式難懂又沒什麼用處. 5. dotnet CSharp 1. 飲料店案例(簡單工廠模式) ([程式碼連結](https://github.com/langley228/DesignPattern/tree/master/src/DesignPattern.CSharp.Factorys.SimpleFactories)) 2. 我的寵物案例(簡單工廠模式、 IoC) 3. 披薩店案例 1. 抽象工廠模式([程式碼連結](https://github.com/langley228/DesignPattern/tree/master/src/DesignPattern.CSharp.Factorys.AbstractFactories)) 2. 抽象工廠改IoC ([程式碼連結](https://github.com/langley228/DesignPattern/tree/master/src/DesignPattern.CSharp.Factorys.Ioc)) 1. 自訂組態區段參考[連結](https://dotblogs.com.tw/yc421206/2015/06/19/151599) 2. 反射/反映(Reflection) [參考連結](https://www.codeproject.com/Questions/331389/Csharp-Reflection-Creating-Instance-from-string) 3. 控制反轉(Inversion of Control,縮寫為IoC)….. [Ioc wiki](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC) ![](https://i.imgur.com/8lfwiZ6.png) > ![class 差異圖]! ![](https://i.imgur.com/Yp8HWKe.png) > 工廠建構子傳入config中的披薩設定值(PizzaElement) , 並利用 反射 (Reflection) 技巧調整原料的 Create Method ![](https://i.imgur.com/JvlZHZy.png) > 披薩不需子類別 (如圖所示) ![](https://i.imgur.com/W2s6SuB.png) > 披薩門市 直接由 PizzaStoreElement 取代 ![](https://i.imgur.com/1nHaEbn.png) > 在config 設定披薩門市與旗下各個披薩口味以及所使用的原料 ,未來新增門市不需增加 工廠、披薩、門市類別,只需要增加新的原料類別即可,而且還可以混搭出新的披薩口味 ![](https://i.imgur.com/JV3GBlE.png) > 測試訂披薩 ![](https://i.imgur.com/ytoihwa.png) ## 獨體模式 [獨體模式投影片連結](https://docs.google.com/presentation/d/1PrwEnIii-evZmksiau_CEGJ9qFokfI5KZ4Bsg-bERec/edit#slide=id.p) Q&A 1. 哪些情境下要用 Singleton Pattern? 2. 使用全域變數有何缺點? A: 名稱空間濫用, 記憶體暴增… 3. 多執行緒使用Singleton Pattern時會發生什麼問題? 4. 請描述多執行緒執行流程? | - .Net C# (程式碼連結) | - VB.Net (程式碼連結)<br> - C# 能做到的事,VB.Net就能做到<br> - VB.Net 也能寫出 Oop 設計模式<br> - 再麻煩都不要放棄 VB.Net | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | //延遲延遲實體化 | //延遲延遲實體化 | | public class Singleton<br>{<br> private static Singleton singleton;<br> private Singleton() { }<br> public static Singleton GetInstance() => singleton ?? (singleton = new Singleton());<br>} | Public Class Singleton<br> Private Shared singleton As Singleton<br> Private Sub New() <br> End Sub<br> Public Shared Function GetInstance() As Singleton<br> If singleton Is Nothing Then<br> singleton = New Singleton()<br> End If<br> Return singleton<br> End Function<br>End Class | | //率先實體化 | //率先實體化 | | public sealed class SealedSingleton<br>{<br> private static readonly SealedSingleton instance = new SealedSingleton();<br> private SealedSingleton() { }<br> public static SealedSingleton GetInstance() => SealedSingleton.instance;<br>} | Public NotInheritable Class SealedSingleton<br> Private Shared instance As SealedSingleton = New SealedSingleton()<br> Private Sub New()<br> End Sub<br> Public Shared Function GetInstance() As SealedSingleton<br> Return instance<br> End Function<br>End Class | | //雙重上鎖 | //雙重上鎖 | | public class SingletonLock<br>{<br> private static SingletonLock instance;<br> private static readonly object syncRoot = new object();<br> private SingletonLock() { }<br> public static SingletonLock GetInstance()<br> {<br> if (instance == null) //確保當前沒有這個物件存在<br> {<br> lock (syncRoot)//鎖住當前狀態<br> {<br> if (instance == null)<br> instance = new SingletonLock();<br> }<br> }<br> return instance;<br> }<br>} | Public Class SingletonLock<br> Private Shared instance As SingletonLock<br> Private Shared ReadOnly syncRoot As Object = New Object()<br> Private Sub New()<br> End Sub<br> Public Shared Function GetInstance() As SingletonLock<br> If instance Is Nothing Then<br> SyncLock syncRoot<br> If instance Is Nothing Then<br> instance = New SingletonLock()<br> End If<br> End SyncLock<br> End If<br> Return instance<br> End Function<br>End Class | | | | - . - Python 3 ![](https://i.imgur.com/AXUtCOa.png) ## 命令模式 Q&A 1.P197,這張圖有甚麼目的描述? A:描述命令模式的Pattern,比對P201的圖,顧客=Client,訂單=Command,女侍=Invoker,快餐廚師=Receiver 2.P203,204降低誰的耦合度? A:Light(Receiver)和 Remote (Invoker)的耦合度,因為是不同廠商所提供,偶和度愈低愈好。 3.P207遙控器是這張圖的那一個 Class? A: Invoker 是遙控器 , Receiver 是設備;另外兩者透過 Command 降低耦合度。 4.P210為什麼要用 noCommand? A:可避免 Null Exception。 5.命令模式的精髓為何? A:把 Receiver 做成包進 Command 裡,這樣可以把 Invoker 和 Receiver 拆離(利用建構子 + interface 把耦合度降低)。 6.P224為何不把 Receiver 都丟進一個 Command 處理,而是把多個 Receiver 的 Command 做成陣列丟進來做巨集處理? A:如果直接丟 Reveiver ,會造成程式碼重複,另外陣列可以繼續堆疊而不用修改程式 ex.先做好一個 開音響 → 開電視,之後要增加一個 開音響 → 開電視 → 關燈,只要增加 COmmand陣列就好。 7.P229這樣的改變有甚麼好處? A:資料庫備份,若無法完整備份,只能差異備份時,可以透過 Command 紀錄下來,這樣可以還原,且Command 很小。 #補充內容 stack, heap 有何用處?記憶體配置? - Heap 動態(堆積, 堆) - 不可預測其存活時間的資料 - 資訊為動態配置產生 - 由於為動態產生故結束點無法由系統來掌握,故需使用者自行回收空間 - Stack 靜態(堆疊, 棧) - 可預測性 - 後進先出的生存模式 - stack中的資料之存活週期規律故由系統自行產生與回收其空間即可 - 常見的存放資訊: 1. 區域變數(local variable) 2. 函式參數(function/method parameter) 3. 函數的返回位址(function/method return address ) ![](https://i.imgur.com/FiJzGsk.png) 補充: https://www.youtube.com/watch?v=450maTzSIvA& https://youtu.be/450maTzSIvA Heap & Stack 練習講解: 問題程式碼如下: public main(){ Foo foo = new Foo(); foo.doA(); foo.doB(); } public class Foo { int a; int b; public class Foo(){ } public void doA(int c; int d){} public void doB(int e int f){} } 講解順序步驟: Step 1. public main(){ Foo foo = new Foo(); Stack | Heap foo Step 2. public main(){ Foo foo = new Foo(); Stack | Heap foo Foo() a, b Step 3. public main(){ Foo foo = new Foo(); foo.doA(); Stack | Heap doA c,d foo Foo() a, b Step 4. public main(){ Foo foo = new Foo(); foo.doA(); foo.doB(); Stack | Heap doB e,f doA c,d foo Foo() a, b | | | class | Stack | Heap | | - | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | --------- | | 1 | void main() <br>{<br>} | | main | | | 2 | void main()<br>{<br> Foo foo=new Foo()<br>} | class Foo<br>{<br> int A<br> int B<br>} | main foo | foo→ A B | | 3 | void main()<br>{<br> Foo foo=new Foo()<br> foo.DoA()<br>} | class Foo<br>{<br> int A<br> int B<br> void DoA()<br> {<br> int C<br> int D <br> }<br>} | DoA C D<br>main foo | foo→ A B | | 4 | void main()<br>{<br> Foo foo=new Foo()<br> foo.DoA()<br>} | class Foo<br>{<br> int A<br> int B<br> void DoA()<br> {<br> int C<br> int D <br> DoB() <br> }<br> void DoB()<br> {<br> int E<br> int F <br> }<br>} | DoB E F<br>DoA C D<br>main foo | foo→ A B | | 5 | | | DoA C D<br>main foo | foo→ A B | | 6 | | | main foo | foo→ A B | | 7 | | | | A B | | 8 | | | | | 另外同一樣在stack內又有區分 ![](https://i.imgur.com/9eGPX37.png) 2. 1 thread v.s. 2 threads? ![](https://i.imgur.com/XMwkM8r.png) ![](https://i.imgur.com/OgckQO0.png) 補充資料: [ 程序、執行緒、多執行緒 ] - 程序(process) 當一個程式在電腦執行時會先向系統進行註冊,讓系統將這個程式記錄在它的執行清單上,系統此時會將這程序標上一個唯一的ID以作為區別,即PID(Process ID),並分配適當的資源給這個程式來進行工作,這些資源包含CPU的使用權以及獨立的資料儲存空間,我們稱此動作為建立一個處理程序(process)。每個程序所擁有的資源在執行期間是獨佔的,不與其它程序共用,所以每個程序只能存取屬於自己的部份,不能直接去更改其它程序的資料,如果程序間需要交流,必須建立通訊的管道才能交換情報。另外,因為CPU的數目有限而待執行的程序可能有很多個,因此系統會為每個程序規劃出一段CPU使用時間,在這段時間內分配給這個程序的CPU就專屬於該程序而不會去處理其它程序的工作。 - 執行緒(thread) 執行緒是系統處理工作的基本單元,通常一個CPU在同一時間只能提供一個執行緒(在此不考慮Hyper-Threading技術),讓系統調用去執行程式。程序跟執行緒間的關係我們可以把它想像成是工廠與員工的關係,工廠擁有資源與設備但需要由員工去操作才能生產產品,所以要做出一件成品,工廠內至少要有一位員工在做事,不同的程序就像是不同的工廠,而執行緒像是一位派遣員工一樣,由作業系統負責調度他在什麼時間該去那家工廠上班,如果電腦擁有多個處理核心,即代表系統可以同時調用多個員工。 - 多執行緒(multi-threading) 延續剛剛工廠、員工的比喻,系統可以同時間內把多個員工派到同一家工廠去工作,此法稱做多執行緒(multi-threading)平行執行,相較於單一執行緒處理方式,它有機會讓工作在比較短的時間內完成,多執行緒工作時,隸屬同一程序下的所有執行緒會分享該程序的所有資源,此外各執行緒彼此間也可以擁有自己私有的資源,而不與其它同一程序內的執行緒共用。這就像是工廠內的公共設備是所有員工可以自由使用一樣,但每個員工也都會有自己的工作空間與置物櫃可以擺放自己私人的物品。 [ 結論 ] - 擁有多CPU的電腦系統可以同時處理多項程序或將單一程序平行處理。 - 一個程序被執行時至少要有一個執行緒,但也可同時擁有多個執行緒來進行處理。 - 程序間的資源是彼此獨立不共用的。 - 同一程序下的所有執行緒分享該程序的所有資源,而執行緒之間也能夠擁有彼此獨立的資源。 ## 轉接器模式與表象模式 名詞定義 轉接器模式 Adapter Pattern 轉接器 Adapter 被轉接者 Adaptee 類別轉接器 Class Adapter 物件轉接器 Object Adapter 表象模式 Facade Pattern 1. p237 生活上有什麼轉接器模式 - VGA轉DVI - Thunderbolt USB - USB插槽可同時支援USB 3.0與USB 2.0介面 2. p239 使用轉接器模式的好處 - 可簡化複雜系統測試上的問題,Adaptee變更時只要修改轉接器內容,不需修改原本程式。 - 例如更換新的廠商library,未來只要更換建構子,並實做對應的介面即可。 - 可侷限錯誤的範圍,確保舊系統的安全。 轉接器做了兩件事 - 透過建構子注入Adaptee - 實作Interface - 透過IoC的機制使用轉接器模式可以達到抽換Adaptee的功能 3. 轉接器模式的定義為何? - 將一個類別的介面,轉換成另一個介面,以供客戶使用。轉接器讓原本介面不相容的類別可以合作無間 4. 類別(Class)轉接器跟物件(Object)轉接器的差異為何? - Class可以接受兩邊次類別的物件,可變更父類別的內容。 - Object透過建構子可以轉接所有該類別的次類別。 5. 裝飾者 vs 轉接器 - 裝飾者:擴充行為和責任,不改變介面,因為是繼承可以呼叫底層(super),本質上使用相同的介面自己包裝自己 - 轉接器:會改變介面,無法改寫被轉接者的內容,主要是針對不同介面的轉換與轉接 6. 表象模式 vs 命令模式的巨集有何差別 - 巨集:只改子命令,跟著更新,可動態變更與抽換,以及佇列的特性可延後執行與進行復原的能力。 - 表象:相較下少寫很多Class,只要一個Facade類別將所有物件包裝。 如果沒有預期未來必須動態變更抽換的需求,選Facade模式即可。 待討論事項 p265-266 最少化原則本身並沒有錯,但舉的例子似乎與最少化原則沒有相符合。大家日後若有想法可再度補充。 ---------- **關於Swift的多重繼承** 在Swift中,類的繼承只能是單繼承。多重繼承可以通過遵從多個(protocol)協議實現。也就是說,在Swift中,一個類只能繼承一個父類,但是可以遵循多個協議。 優點:可擴充型別 Object-Oriented Programming (OOP) 是近年軟體開發的主流設計模式,但在 2015 WWDC,Apple 對這個稱霸數十載的設計模式提出挑戰。 Apple 認為,在 OOP 的架構中,假如 super class 定義過於狹窄,能繼承於此 super class 的 sub class 就會較少;為了讓 super class 較「通用」、能被較多 sub classes 繼承,開發者往往需要在 super class 中加入許多 properties,但這些 properties 卻未必是每個 sub class 都需要的。於是,OOP 為主的專案容易龐雜、且程式碼容易缺乏彈性。 於是,Apple 提出 Protocol Oriented Programming(POP),主張應該用 protocol 當作設計程式架構的基礎,以解決 OOP 容易龐雜、缺乏彈性的缺點。 對 POP 細節有興趣的讀者可以自行觀看 WWDC 影片。總之,class 能夠被繼承的特性是 OOP 的核心,但 class 有效率較差、資料容易被誤改(因為 reference type 的特性)的問題。假如改用 OOP 以外的設計模式(例如POP),我們就不必常常依賴 class 的繼承特性,自然就可以優先使用效率較佳、資料較「安全」(因為 value type 的特性)的 struct。 歸根究柢,Apple 在近期的文間開始推廣 struct,主要還是因為 Apple 在推廣 POP,在 POP 下,使用 struct 大部分的時候比使用 class 更明智。 範例: Protocol 多重繼承 Swift 不支援`class`多重繼承機制,但可以繼承多個`protocol`。 protocol Movable { var x: Int { get set } var y: Int { get set } func move(x: Int, y: Int) } protocol Resizable { var w: Int { get set } var h: Int { get set } func resize(w: Int, h: Int) } class Rect: Movable, Resizable { var x: Int = 0 var y: Int = 0 var w: Int = 10 var h: Int = 10 func move(x: Int, y: Int) { self.x = x self.y = y } func resize(w: Int, h: Int) { self.w = w self.h = h } } 同時繼承類別與協定,應先宣告繼承`class`,再宣告`protocol`。 protocol Movable { func move(x: Int, y: Int) } protocol Colorful { var color: String { get set } } class Point { var x: Int = 0 var y: Int = 0 } class ColorPoint: Point, Movable, Colorful { var color: String = "blue" func move(x: Int, y: Int) { self.x = x self.y = y } } var point = ColorPoint() //return: {{x 0 y 0} color “blue"} ## 樣板方法模式 優點: 1. 次類別無需知道樣板仍能正確的執行 (因為父類別已定義樣板) 2. 定義需改變的方法定義為abstract, 交由子類別完成, 能避免子類別漏做 3. 程式碼不會重複 4. 演算法集中未散落 hook例子: 1. android onCreate 1. onCreate可進行異動改變行為, 但原先流程仍會執行 2. Set<object>時, 如果需要利用Set排除相同元素時, object需implement equals()方法 好萊塢守則: 1. 避免依賴腐敗 2. 次類別不呼叫父類別元件 (低階元件不呼叫高階元件) 3. 避免環狀依賴(少見) 4. 避免父類別影響子類別的實作 為何java的sort()樣板使用組合並設定成static 1. 因java 沒有多重繼承所以設計上用組合 (後面的字看不懂…等俊瑋來補XD) 2. 使用上默認共用性較高的地方定在static上使用, 而讓instance方法較為依賴物件特性 ## 反覆器與合成模式 - Set 最簡單的一種集合, 集合中的物件不按特定的方式排序, 並且沒有重復物件 - HashSet : 按照哈希算法來存取集合中的物件, 存取速度比較快 - TreeSet : 實現了 SortedSet 介面, 能夠對集合中的物件進行排序 - Map 把 key 和 value 映射的集合, 它的每一個元素都包含一對 key 和 value Map 沒有繼承於 Collection 介面, 從 Map 集合中檢索元素時, 只要給出 key, 就會返回對應的value - List 元素以線性方式存儲, 集合中可以存放重復物件 主要實現類包括 - ArrayList : 大量資料讀取速度快, 插入與刪除元素的速度慢 - LinkedList : 插入和刪除速度快, 大量資料讀取速度慢 合成 - Clinet 眼中把所有當成 componet - 差別在於 Leaf 跟 composite - compomponent 為 abstract 使得無法 new , 以避免被破壞結構 Top ---------- 狀態模式 1.P403 為什麼 releaseBall 這個方法會放在 GumBallMachine 裡? A:因為這個情形會修改 GumBallMachine 的 property,以封裝的概念,不應該由 State 處理。 2.P405 動動腦 A: 1.回傳 true/false 判斷是否繼續執行 (但丟出來的 bool 意義不明,不看文件不知道作用 2.丟 exception (對程式處理方式不一致,不建議使用例外處理程式邏輯 3.改由 turnCrank 呼叫 dispense 3.狀態模式 VS. 策略模式 A:兩者的差異在程式外部知不知道內部合成類別的變化。 在狀態模式時外部不知道內部變化,由狀態物件決定。 在策略模式時外部知道內部變化,而且是由外部做改變。 Top ---------- 代理人模式 RMI → 客戶物件 & 遠端物件 transient 使 state 中的 GumballMachine 不會產生遞迴 VirtualProxy 應用 → 同步轉為非同步 Top ---------- 補充筆記 - 記憶體配置