From 1ab706016efc9b97c00f4e9aba3b15c90503f805 Mon Sep 17 00:00:00 2001 From: Petrutiu Mihai Date: Wed, 22 Jun 2016 16:24:25 +0300 Subject: [PATCH] Add command pattern --- BehavioralPatterns.sln | 7 +++ src/BehavioralPatterns/Program.cs | 3 ++ src/BehavioralPatterns/project.json | 1 + src/CommandPattern/CommandPattern.xproj | 21 ++++++++ src/CommandPattern/CommandPatternExamples.cs | 45 +++++++++++++++++ src/CommandPattern/Program.cs | 14 ++++++ src/CommandPattern/Properties/AssemblyInfo.cs | 19 +++++++ src/CommandPattern/StocksExample/Agent.cs | 47 +++++++++++++++++ .../StocksExample/Commands/BuyStockOrder.cs | 24 +++++++++ .../StocksExample/Commands/SellStockOrder.cs | 23 +++++++++ .../StocksExample/FixedSizedQueue.cs | 29 +++++++++++ src/CommandPattern/StocksExample/Order.cs | 15 ++++++ src/CommandPattern/StocksExample/Stock.cs | 8 +++ .../StocksExample/StockExampleRunner.cs | 30 +++++++++++ .../StocksExample/StockSchedule.cs | 50 +++++++++++++++++++ src/CommandPattern/StocksExample/StocksAPI.cs | 20 ++++++++ src/CommandPattern/project.json | 19 +++++++ 17 files changed, 375 insertions(+) create mode 100644 src/CommandPattern/CommandPattern.xproj create mode 100644 src/CommandPattern/CommandPatternExamples.cs create mode 100644 src/CommandPattern/Program.cs create mode 100644 src/CommandPattern/Properties/AssemblyInfo.cs create mode 100644 src/CommandPattern/StocksExample/Agent.cs create mode 100644 src/CommandPattern/StocksExample/Commands/BuyStockOrder.cs create mode 100644 src/CommandPattern/StocksExample/Commands/SellStockOrder.cs create mode 100644 src/CommandPattern/StocksExample/FixedSizedQueue.cs create mode 100644 src/CommandPattern/StocksExample/Order.cs create mode 100644 src/CommandPattern/StocksExample/Stock.cs create mode 100644 src/CommandPattern/StocksExample/StockExampleRunner.cs create mode 100644 src/CommandPattern/StocksExample/StockSchedule.cs create mode 100644 src/CommandPattern/StocksExample/StocksAPI.cs create mode 100644 src/CommandPattern/project.json diff --git a/BehavioralPatterns.sln b/BehavioralPatterns.sln index 4b15cf4..cb539ee 100644 --- a/BehavioralPatterns.sln +++ b/BehavioralPatterns.sln @@ -14,6 +14,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "BehavioralPatterns", "src\B EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ChainOfResponssibility", "src\ChainOfResponssibility\ChainOfResponssibility.xproj", "{89536824-683F-4351-8789-406D7BDD922D}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CommandPattern", "src\CommandPattern\CommandPattern.xproj", "{454B2A43-8251-4667-8DE3-67E489908DB9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -28,6 +30,10 @@ Global {89536824-683F-4351-8789-406D7BDD922D}.Debug|Any CPU.Build.0 = Debug|Any CPU {89536824-683F-4351-8789-406D7BDD922D}.Release|Any CPU.ActiveCfg = Release|Any CPU {89536824-683F-4351-8789-406D7BDD922D}.Release|Any CPU.Build.0 = Release|Any CPU + {454B2A43-8251-4667-8DE3-67E489908DB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {454B2A43-8251-4667-8DE3-67E489908DB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {454B2A43-8251-4667-8DE3-67E489908DB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {454B2A43-8251-4667-8DE3-67E489908DB9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -35,5 +41,6 @@ Global GlobalSection(NestedProjects) = preSolution {E3092EE0-1282-4AB4-9FA2-0338348D8FD1} = {3820200F-354C-41E6-8F34-B301F5D621C2} {89536824-683F-4351-8789-406D7BDD922D} = {3820200F-354C-41E6-8F34-B301F5D621C2} + {454B2A43-8251-4667-8DE3-67E489908DB9} = {3820200F-354C-41E6-8F34-B301F5D621C2} EndGlobalSection EndGlobal diff --git a/src/BehavioralPatterns/Program.cs b/src/BehavioralPatterns/Program.cs index a8c7f30..9cd1b02 100644 --- a/src/BehavioralPatterns/Program.cs +++ b/src/BehavioralPatterns/Program.cs @@ -1,5 +1,6 @@ using ChainOfResponssibility; using ChainOfResponssibility.PurchaseExample; +using CommandPattern; using System; using System.Collections.Generic; using System.Diagnostics; @@ -16,6 +17,8 @@ namespace BehavioralPatterns //This is usefull when you have a request and you don't know who should process it ChainOfResponsibillityExamples.Run(); Console.ReadKey(); + CommandPatternExamples.Run(); + Console.ReadKey(); } } } diff --git a/src/BehavioralPatterns/project.json b/src/BehavioralPatterns/project.json index f111bfd..ba86bcd 100644 --- a/src/BehavioralPatterns/project.json +++ b/src/BehavioralPatterns/project.json @@ -6,6 +6,7 @@ "dependencies": { "ChainOfResponssibility": "1.0.0-*", + "CommandPattern": "1.0.0-*", "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0-rc2-3002702" diff --git a/src/CommandPattern/CommandPattern.xproj b/src/CommandPattern/CommandPattern.xproj new file mode 100644 index 0000000..4eff81a --- /dev/null +++ b/src/CommandPattern/CommandPattern.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 454b2a43-8251-4667-8de3-67e489908db9 + CommandPattern + .\obj + .\bin\ + v4.6.1 + + + + 2.0 + + + diff --git a/src/CommandPattern/CommandPatternExamples.cs b/src/CommandPattern/CommandPatternExamples.cs new file mode 100644 index 0000000..f174510 --- /dev/null +++ b/src/CommandPattern/CommandPatternExamples.cs @@ -0,0 +1,45 @@ +using CommandPattern.StocksExample; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CommandPattern +{ + public class CommandPatternExamples + { + public static void Run() + { + Console.WriteLine(GetPatternDescription()); + GoToNextStep(); + + StockExampleRunner stockExampleRunner = new StockExampleRunner(); + Console.WriteLine(stockExampleRunner.GetDescriptionOfExample()); + GoToNextStep(); + stockExampleRunner.Run(); + stockExampleRunner.Run(); + stockExampleRunner.Run(); + stockExampleRunner.Run(); + } + + private static void GoToNextStep() + { + Console.ReadKey(); + Console.Clear(); + } + + public static string GetPatternDescription() + { + return @" +command pattern is a behavioral design pattern in which an object is used to encapsulate +all information needed to perform an action or trigger an event at a later time +Uses: +1. Macro recording: f all user actions are represented by command objects, a program can record a +sequence of actions simply by keeping a list of the command objects as they are executed. +2. Undo +3. GUI buttons and menu items +4. Parallel processing +5. Transactional behavior "; + } + } +} diff --git a/src/CommandPattern/Program.cs b/src/CommandPattern/Program.cs new file mode 100644 index 0000000..49f01ca --- /dev/null +++ b/src/CommandPattern/Program.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CommandPattern +{ + public class Program + { + public static void Main(string[] args) + { + } + } +} diff --git a/src/CommandPattern/Properties/AssemblyInfo.cs b/src/CommandPattern/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d534ac9 --- /dev/null +++ b/src/CommandPattern/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Hewlett-Packard Company")] +[assembly: AssemblyProduct("CommandPattern")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("454b2a43-8251-4667-8de3-67e489908db9")] diff --git a/src/CommandPattern/StocksExample/Agent.cs b/src/CommandPattern/StocksExample/Agent.cs new file mode 100644 index 0000000..5b67170 --- /dev/null +++ b/src/CommandPattern/StocksExample/Agent.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CommandPattern.StocksExample +{ + /// + /// Invoker. The invoker will invoke the command + /// + public class Agent + { + Stack ordersNotExecuted; + FixedSizedQueue ordersPlaced; + StockSchedule stockSchedule; + + public Agent(StockSchedule stockSchedule) + { + ordersNotExecuted = new Stack(); + ordersPlaced = new FixedSizedQueue(10); + this.stockSchedule = stockSchedule; + stockSchedule.StockExchangedOpened += StockSchedule_StockExchangedOpened; + } + + private void StockSchedule_StockExchangedOpened(object sender, EventArgs e) + { + while (ordersNotExecuted.Any()) + { + PlaceOrder(ordersNotExecuted.Pop()); + } + } + + public void PlaceOrder(Order order) + { + if(stockSchedule.IsStockOpen()) + { + order.Execute(); + ordersPlaced.Enqueue(order); + } + else + { + Console.WriteLine("Market is not opened, so the order was saved for later"); + ordersNotExecuted.Push(order); + } + } + } +} diff --git a/src/CommandPattern/StocksExample/Commands/BuyStockOrder.cs b/src/CommandPattern/StocksExample/Commands/BuyStockOrder.cs new file mode 100644 index 0000000..8d70918 --- /dev/null +++ b/src/CommandPattern/StocksExample/Commands/BuyStockOrder.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CommandPattern.StocksExample.Commands +{ + //Concrete Command + public class BuyStockOrder : Order + { + StocksAPI stocksAPI; + Stock stock; + public BuyStockOrder(StocksAPI stocksAPI, Stock stock) + { + this.stocksAPI = stocksAPI; + this.stock = stock; + } + + public void Execute() + { + stocksAPI.Buy(stock); + } + } +} diff --git a/src/CommandPattern/StocksExample/Commands/SellStockOrder.cs b/src/CommandPattern/StocksExample/Commands/SellStockOrder.cs new file mode 100644 index 0000000..9cabb88 --- /dev/null +++ b/src/CommandPattern/StocksExample/Commands/SellStockOrder.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CommandPattern.StocksExample.Commands +{ + public class SellStockOrder : Order + { + StocksAPI stocksAPI; + Stock stock; + public SellStockOrder(StocksAPI stocksAPI, Stock stock) + { + this.stocksAPI = stocksAPI; + this.stock = stock; + } + + public void Execute() + { + stocksAPI.Sell(stock); + } + } +} diff --git a/src/CommandPattern/StocksExample/FixedSizedQueue.cs b/src/CommandPattern/StocksExample/FixedSizedQueue.cs new file mode 100644 index 0000000..18b2cb4 --- /dev/null +++ b/src/CommandPattern/StocksExample/FixedSizedQueue.cs @@ -0,0 +1,29 @@ +using System.Collections.Concurrent; + +namespace CommandPattern.StocksExample +{ + public class FixedSizedQueue : ConcurrentQueue + { + private readonly object syncObject = new object(); + + public int Size { get; private set; } + + public FixedSizedQueue(int size) + { + Size = size; + } + + public new void Enqueue(T obj) + { + base.Enqueue(obj); + lock (syncObject) + { + while (Count > Size) + { + T outObj; + TryDequeue(out outObj); + } + } + } + } +} diff --git a/src/CommandPattern/StocksExample/Order.cs b/src/CommandPattern/StocksExample/Order.cs new file mode 100644 index 0000000..82079d9 --- /dev/null +++ b/src/CommandPattern/StocksExample/Order.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CommandPattern.StocksExample +{ + /// + /// Command interface + /// + public interface Order + { + void Execute(); + } +} diff --git a/src/CommandPattern/StocksExample/Stock.cs b/src/CommandPattern/StocksExample/Stock.cs new file mode 100644 index 0000000..251b74b --- /dev/null +++ b/src/CommandPattern/StocksExample/Stock.cs @@ -0,0 +1,8 @@ +namespace CommandPattern.StocksExample +{ + public class Stock + { + public string Name { get; set; } + public int Quantity { get; set; } + } +} \ No newline at end of file diff --git a/src/CommandPattern/StocksExample/StockExampleRunner.cs b/src/CommandPattern/StocksExample/StockExampleRunner.cs new file mode 100644 index 0000000..d51efb6 --- /dev/null +++ b/src/CommandPattern/StocksExample/StockExampleRunner.cs @@ -0,0 +1,30 @@ +using CommandPattern.StocksExample.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CommandPattern.StocksExample +{ + public class StockExampleRunner + { + public void Run() + { + StocksAPI stocksAPI = new StocksAPI(); + Agent agent = new Agent(new StockSchedule()); + + Stock stock = new Stock { Name = "AAPL", Quantity = 20 }; + + agent.PlaceOrder(new BuyStockOrder(stocksAPI, stock)); + agent.PlaceOrder(new SellStockOrder(stocksAPI, stock)); + } + + public string GetDescriptionOfExample() + { + return @" +Buy or sell a stock on the market. +If the market is closed save the orders for when the market opens again. +When the market opens place all the orders."; + } + } +} diff --git a/src/CommandPattern/StocksExample/StockSchedule.cs b/src/CommandPattern/StocksExample/StockSchedule.cs new file mode 100644 index 0000000..1e33321 --- /dev/null +++ b/src/CommandPattern/StocksExample/StockSchedule.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading; + +namespace CommandPattern.StocksExample +{ + public class StockSchedule + { + TimeSpan openingTime; + public StockSchedule() + { + CheckForOpeningOfStockExchange(); + openingTime = new TimeSpan(9, 0, 0); + } + + public event EventHandler StockExchangedOpened; + + public bool IsStockOpen() + { + return new Random().NextDouble() > 0.5; + } + + private void OnStockExchangedOpened(object state) + { + StockExchangedOpened?.Invoke(this, null); + Thread.Sleep(1); + CheckForOpeningOfStockExchange(); + + } + + private void CheckForOpeningOfStockExchange() + { + var t = new Timer(new TimerCallback(OnStockExchangedOpened), null, Timeout.Infinite, Timeout.Infinite); + + // Figure how much time until opening market + DateTime now = DateTime.Now; + DateTime openingTime = DateTime.Today.Add(this.openingTime); + + // If it's already past opening time, wait until opening time tomorrow + if (now > openingTime) + { + openingTime = openingTime.AddDays(1.0); + } + + int msUntilOpeningHour = (int)((openingTime - now).TotalMilliseconds); + + // Set the timer to elapse only once, at opening time. + t.Change(msUntilOpeningHour, Timeout.Infinite); + } + } +} \ No newline at end of file diff --git a/src/CommandPattern/StocksExample/StocksAPI.cs b/src/CommandPattern/StocksExample/StocksAPI.cs new file mode 100644 index 0000000..1c48457 --- /dev/null +++ b/src/CommandPattern/StocksExample/StocksAPI.cs @@ -0,0 +1,20 @@ +using System; + +namespace CommandPattern.StocksExample +{ + /// + /// Receiver of the command. Concrete command is executing code from this class + /// + public class StocksAPI + { + public void Buy(Stock stock) + { + Console.WriteLine("Stock [ Name: {0}, Quantity: {1} bought", stock.Name, stock.Quantity); + } + + public void Sell(Stock stock) + { + Console.WriteLine("Stock [ Name: {0}, Quantity: {1} sold", stock.Name, stock.Quantity); + } + } +} diff --git a/src/CommandPattern/project.json b/src/CommandPattern/project.json new file mode 100644 index 0000000..10c7989 --- /dev/null +++ b/src/CommandPattern/project.json @@ -0,0 +1,19 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": "dnxcore50" + } + } +}