/* Finding an Error ... This program implements the Observer Design Pattern, one of the most commonly applied patterns of object oriented programming. It contains a memory-related error. You need to find and explain the error. In this program, the "observers" are stock investors and the "observed" are different stocks. There are different types of investors: "bull" investors, "bear" investors and "panicky" investors. The type `investor` is an abstract class that all these subclasses inherit from. The class `stock` implements different stocks, defined by a string `symbol` and its current price as a float. First look at main, which should be self-explanatory. This is exactly what we want from a good OOP design: self-explanatory code with all the details encapsulated inside the objects. Each subclass of investor implements a method "update(stock s)": this method is called when a stock's price changes. The update functions implement the investment strategies of the different types of investors. Each investor has a `portfolio` that includes pointers to the stocks. It need these pointers so it can call certain methods on the stocks, such as the `trend` method that returns the average change in price over a number of days. It calls such methods so it can determine to buy or sell shares, or dump the stock by selling all shares and removing the stock from its portfolio. Each stock also must maintain a structure that contains pointers to investors so it can update them, via the `notify` method, when its price changes. Each stock also have methods `attach`, which is called when a new investor invests in it, and `detach`, which is called when an investors dumps all shares of the stock. A programming language specifically designed for OOP, such as Java/C#, is probably best suited for implementing such a program. Dynamic memory allocation and dynamic dispatch would be required anyway. Performance is a secondary concern in applications such as this: it just needs to be fast enough. There are multiple pointers to each object: pointers can't be unique. Furthermore, the pointers between investors and stocks form cycles in memory: a terrifying situation for languages without a garbage collector (even Rust doesn't have a perfect solution for such situations). A garbage collector generates a spanning tree with the pointers, an expensive operation but which means it can't be trapped by the cycles. Programming is certainly easier with a garbage collector, but there can still be subtle, memory-related errors, as this program demonstrates. */ using System; using System.Collections.Generic; abstract class investor { public readonly string name; // portfolio maps stock names to pointers to stock + number of shares protected Dictionary> portfolio; public investor(string n) { name=n; portfolio = new Dictionary>(); } // constructor public void invest(stock sptr, int shares) { sptr.attach(this); portfolio[sptr.symbol] = new KeyValuePair(sptr,shares); } public void buy(string stocksym, int shares) { var entry = portfolio[stocksym]; portfolio[stocksym] = new KeyValuePair(entry.Key,entry.Value+shares); } public void sell(string stocksym, int shares) { buy(stocksym,-1*shares);} public void dump(stock sptr) { portfolio.Remove(sptr.symbol); sptr.detach(name); } public abstract void update(stock observed); // implemented by subclasses } // investor ///// Concrete Observers class bullinvestor : investor { public bullinvestor(string n):base(n) {} public override void update(stock s) { buy(s.symbol,100); // buy more regardless! be bullish! Console.WriteLine(name+" is buying more "+s.symbol); }// bull investment strategy }//bullinvestor class bearinvestor : investor { public bearinvestor(string n) : base(n) {} public override void update(stock s) { float diff = s.trend(3); if (diff>0.5f) buy(s.symbol,10); else if (diff>3.0f) sell(s.symbol,10); // sell before bubble bursts Console.WriteLine(name+" now owns "+portfolio[s.symbol].Value+" shares of "+s.symbol); }// bear investment strategy }//bullinvestor class panickyinvestor : investor { public panickyinvestor(string n):base(n) {} public override void update(stock s) { float diff = s.trend(3); if (diff < -0.1f) { dump(s); Console.WriteLine(name+" dumped all shares of "+s.symbol); } }// panicky investment strategy }//panickyinvestor /////// Observed Objects class stock { public readonly string symbol; List prices = new List(); // using List for a stack Dictionary investors = new Dictionary(); public stock(string s, float p) { symbol=s; prices.Add(p); } //constructor // observer pattern methods public void attach(investor observer) { investors[observer.name] = observer; } public void detach(string investor_name) { investors.Remove(investor_name); } void notify() { foreach(KeyValuePair kvp in investors) { kvp.Value.update(this); } }//notify public float current_price() { return prices[prices.Count-1]; } public void price_change(float dp) { float newprice = current_price() + dp; prices.Add(newprice); notify(); } // computes average change in price over past number of days public float trend(int days) { if (days<2 || prices.Count<2) return 0.0f; float sumdiff = 0.0f; // sum of differences for(int i = prices.Count-1; i>0 && i>prices.Count-days; i--) { sumdiff += prices[i] - prices[i-1]; } return sumdiff/days; // nonexistent days will return zero }//trend }//stock public class observers_problem { public static void Main() { Random rand = new Random(); // for simulations stock[] Stocks = { new stock("INTC",28.5f), // intel new stock("AAPL",141.48f), // apple new stock("MSFT",242.4f), // microsoft new stock("TSLA",181.9f), // tesla new stock("GOOGL", 96.16f), // google new stock("DANOY",10.38f), //leading manufacturer of farm feed }; investor bull = new bullinvestor ("big bull"); investor bear = new bearinvestor("little bear"); investor bbear = new bearinvestor("big bear"); investor panic = new panickyinvestor("panicky"); foreach(var s in Stocks) bull.invest(s,100); //bull buys 100 shares of each for(int i=0;i<5;i++) bear.invest(Stocks[i],50); //bears buy 50 shares each for(int i=0;i<5;i++) bbear.invest(Stocks[i],100); panic.invest(Stocks[3],20); panic.invest(Stocks[4],30); // simulate stock price changes foreach(var s in Stocks) s.price_change(rand.Next(400)/100.0f-2.0f); //+/-2 foreach(var s in Stocks) s.price_change(rand.Next(400)/100.0f-2.0f); foreach(var s in Stocks) s.price_change(rand.Next(400)/100.0f-2.0f); foreach(var s in Stocks) s.price_change(rand.Next(400)/100.0f-2.0f); // everything else is triggered automatically }//main }