1、觀察者模式簡介:java
首先看百度百科上對觀察者模式的簡介:觀察者模式(Observer)完美的將觀察者和被觀察的對象分離開。舉個例子,用戶界面能夠做爲一個觀察者,業務數據是被觀察者,用戶界面觀察業務數據的變化,發現數據變化後,就顯示在界面上。面向對象設計的一個原則是:系統中的每一個類將重點放在某一個功能上,而不是其餘方面。一個對象只作一件事情,而且將他作好。觀察者模式在模塊之間劃定了清晰的界限,提升了應用程序的可維護性和重用性。算法
觀察者模式體現了系統內模塊之間存在的1:n的依賴關係,其中 1在整個系統中被稱做主題(Subject),n在系統中被稱做觀察者(Observer)。在整個系統中,這些觀察者均須要利用主題的狀態來決定自身的狀態,當主題的狀態發生改變時,這些觀察者須要能被通知到並改變自身的狀態。同時,當向系統中增長新的觀察者時不能對系統中已存在的觀察者和主題的代碼形成影響,即需知足軟件設計模式的開-閉原則,所以須要運用到抽象接口等技術。在觀察者模式中存在如下幾個角色:設計模式
那麼問題來了,爲何須要這麼設計呢?其實在系統中,主題可能不止一個,觀察者能夠同時觀察多個主題,兩邊造成了多對多的關係。衆多主題均有一個不變的特性—通知:當本身的狀態發生變化時通知觀察本身的觀察者狀態發生變化;衆多觀察者均有一個不變的特性—更新:當收到主題的狀態改變信號時對應的更新本身的狀態。所以須要把這兩個不變的部分從中抽取出來即分離變化,於是須要設計Subject抽象主題以及Observer抽象觀察者。舉個簡單的例子,當學校週一進行一週彙報時老師不可能將一週工做和每一個同窗一一通知到位,不一樣的老師會有不一樣的信息,但他們均採用廣播通報的方式進行工做彙報,所以廣播通報的方式是老師這個主題不變的特性;同時學生接受信息的時候均具備聽這一特性,但每一個學生的聽的方式卻各有不一樣。這就很好的說明了觀察者模式在生活中有不少的應用。在數據發生變化的時候只需經過訪問接口類的更新方法便可,在添加新的觀察者或者主題時只需實現相應的接口便可。數組
如下爲觀察者模式的類圖:數據結構
2、觀察者模式在股票分析中的應用app
股票的價格常常處於一種瞬息萬變狀態之中,股民想要從中獲利必需要對股票近段時間的數據走向進行分析,不少炒股軟件均提供了數據分析的功能,其中根據股票價格的變化提供分時圖,K線圖均是很常見的功能。從以上的描述中能夠得出股票交易是具體主題,分時圖、K線圖則是具體觀察者。當股票的價格發生變化時,分時圖和K線圖須要可以根據股票價格的變換而相應的調整自身的狀態圖,所以對於股票價格的數據分析能夠經過觀察者模式來解決。爲了簡化問題來模擬股市的變化,咱們先作出如下假設:框架
在以上的假設的基礎上,咱們針對本問題進行設計:dom
一、設計4個基本角色:ide
①抽象主題Subject(包含三種抽象方法):學習
package Subject; import Observer.Observer; public interface Subject { public void addObserver(Observer o); public void deleteObserver(Observer o); public void notifyObservers(); }
②抽象觀察者Observer(包含抽象方法update):
package Observer; public interface Observer { public void drawFig(); }
③具體主題StockDealSubject(實現抽象接口Subject模擬股市交易):在觀察者模式中觀察者具備兩種獲取數據的方式:推數據和拉數據。其中推數據方式是指具體主題將變化後的數據所有傳給具體觀察者,即將變化後的數據做爲參數進行傳遞;而拉數據是指具體主題不將變化後的數據交給具體觀察者,而是提供獲取這些數據的額方法,具體觀察者在獲得通知後能夠調用具體主題的方法獲得數據,就至關於觀察者本身將數據拉了過來,故稱做拉數據。由於我設計的圖形化界面是經過用戶點擊對應的按鈕產生對應的分析圖,故在這裏我採用的是拉數據的方式。
package Subject; import java.util.*; import Observer.Observer; public class StockDealSubject implements Subject{ double[][] prices; String type; ArrayList<Observer> observerList; public StockDealSubject(){ observerList = new ArrayList<Observer>(); prices = null; } @Override public void addObserver(Observer o) { // TODO Auto-generated method stub if(!(observerList.contains(o))){ observerList.add(o); } } @Override public void deleteObserver(Observer o) { // TODO Auto-generated method stub if(observerList.contains(o)){ observerList.remove(o); } } @Override public void notifyObservers() { // TODO Auto-generated method stub for(int i=0;i<observerList.size();i++){ Observer observer = observerList.get(i); observer.drawFig(); } } public void setPriceandType(double[][] prices,String type){ this.prices = prices; this.type = type; } public String getType(){ return this.type; } public double[][] getPrices(){ return this.prices; } }
④具體觀察者TimeFigure(分時圖)和KlineFigure(K線圖):這兩個具體觀察者均實現了抽象觀察者中的drawFig()的更新方法。在具體的drawFig方法中,因爲我設計的圖形界面經過用戶點擊對應的按鈕產生對應的分析圖,所以須要當前的更新操做是不是對本身這個觀察者的更新操做。在數據結構的設計方面,我採用的是二維數組prices[m][n],其含義爲第m支股票在第n/120+1天的第n%120分鐘的收益爲prices[m][n]。對於K線圖的繪製,我自定義了數據類Klineobject,其中分別包含了K線圖的四個基本屬性:開盤價、收盤價、當天交易額最大值、當天交易額最小值,並用ArrayList來存儲天天的屬性。在這些數據及結構的基礎上,須要對漲停和跌停這兩種狀況進行限定,故須要對隨機產生的股票價格數據進行處理,使得其在知足條件的範圍內,得出新的數據後則須要根據這些數據來進行繪圖。
//TimeFigure分時圖 package Observer; import java.awt.GridLayout; import javax.swing.JFrame; import org.jfree.chart.ChartPanel; import DrawMethod.DrawLineChart; import Subject.StockDealSubject; import Subject.Subject; public class TimeFigure implements Observer{ Subject subject; String type; public TimeFigure(Subject subject){ this.subject = subject; this.type = "TimeFigure"; subject.addObserver(this); } @Override public void drawFig() { /* * 假設第一天的前一天的收盤價爲每股100元,漲跌幅爲10%; * 假設共有四支股票,模擬10天內的股票分時圖走向; * 假設天天股票交易時間長度爲2小時,時間段爲9:00~11:00,用1200個數據來模擬每隻股票的價格走向; * 假設最後10筆交易的平均值做爲當天的收盤價,最初10筆加一的平均值爲當天的開盤價; */ if(subject instanceof StockDealSubject){ String type = ((StockDealSubject) subject).getType(); double[][] prices = ((StockDealSubject) subject).getPrices(); int n = prices.length; int size = prices[0].length; if(type.equals(this.type)){ //根據股票的價格變更和漲跌停狀況調整每支股票的價格數組; for(int i=0;i<n;i++){ double yesterdayClosePrice=100; double last10TotalPrice=0; double limitUpPrice = yesterdayClosePrice*1.1; double limitDownPrice = yesterdayClosePrice*0.9; for(int j=0;j<size;j++){ if(j%120==0&&j!=0){ //次日最後10筆交易清空並改動漲跌價和昨天收盤價; yesterdayClosePrice=last10TotalPrice/10.0; limitUpPrice = yesterdayClosePrice*1.1; limitDownPrice = yesterdayClosePrice*0.9; last10TotalPrice=0; } //每分鐘進行的交易額須要判斷是否漲停或者跌停; if(prices[i][j]<=limitUpPrice&&prices[i][j]>=limitDownPrice){ prices[i][j]=prices[i][j]; } else if(prices[i][j]>limitUpPrice){ prices[i][j]=limitUpPrice; } else{ prices[i][j]=limitDownPrice; } if(j%120>=110&&j%120<=119){ //統計最後10筆交易的總額; last10TotalPrice+=prices[i][j]; } } } //獲得新的價格數組後進行繪圖; DrawLineChart drawline = new DrawLineChart(prices); ChartPanel frame1 = drawline.getChartPanel(); JFrame frame=new JFrame("Java數據統計圖"); frame.add(frame1); frame.setBounds(50, 50, 800, 600); frame.setVisible(true); } } } }
//KlineFigure K線圖 package Observer; import Subject.StockDealSubject; import Subject.Subject; import java.awt.font.*; import java.util.ArrayList; import javax.swing.JFrame; import Main.Klineobject; import org.jfree.chart.ChartPanel; import DrawMethod.DrawKlineChart; public class KlineFigure implements Observer{ Subject subject; String type; public KlineFigure(Subject subject){ this.subject = subject; type = "KlineFigure"; subject.addObserver(this); } @Override public void drawFig() { // TODO Auto-generated method stub if(subject instanceof StockDealSubject){ double[][] prices = ((StockDealSubject) subject).getPrices(); int n = prices.length; int size = prices[0].length; String type = ((StockDealSubject) subject).getType(); if(type.equals(this.type)){ //根據股票的價格變更和漲跌停狀況調整每支股票的價格數組,並將天天的統計數據加入到List中; ArrayList<Klineobject> objects = new ArrayList<>(); for(int i=0;i<n;i++){ double yesterdayClosePrice=100;//前一天的收盤價; double last10TotalPrice=0;//當天最後成交的10筆交易總額; double first10TotalPrice=0;//當天前10筆成交總額; double limitUpPrice = yesterdayClosePrice*1.1;//漲停價; double limitDownPrice = yesterdayClosePrice*0.9;//跌停價; double startPrice = 0;//開盤價; double maxDealDay = Double.MIN_VALUE;//當天交易額最大 double minDealDay = Double.MAX_VALUE;//當天交易額最小 for(int j=0;j<size;j++){ if(j%120==0&&j!=0){ //到次日最後10筆交易清空並改動漲跌價和昨天收盤價; yesterdayClosePrice=last10TotalPrice/10.0; limitUpPrice = yesterdayClosePrice*1.1; limitDownPrice = yesterdayClosePrice*0.9; last10TotalPrice=0; first10TotalPrice=0; } //每分鐘進行的交易額須要判斷是否漲停或者跌停; if(prices[i][j]<=limitUpPrice&&prices[i][j]>=limitDownPrice){ prices[i][j]=prices[i][j]; } else if(prices[i][j]>limitUpPrice){ prices[i][j]=limitUpPrice; } else{ prices[i][j]=limitDownPrice; } //判斷最高價和最低價 maxDealDay = Math.max(maxDealDay, prices[i][j]); minDealDay = Math.min(minDealDay, prices[i][j]); //得出開盤價:天天前10筆交易 if(j%120>=0&&j%120<=9){ first10TotalPrice+=prices[i][j]; } //統計最後10筆交易的總額; if(j%120>=110&&j%120<=119){ last10TotalPrice+=prices[i][j]; } //統計當天的數據; if(j%120==119){ Klineobject object = new Klineobject(first10TotalPrice/10, last10TotalPrice/10, maxDealDay, minDealDay); objects.add(object); //最大最小額重置 maxDealDay = Double.MIN_VALUE; minDealDay = Double.MAX_VALUE; } } } DrawKlineChart drawkline = new DrawKlineChart(objects,size/120); ChartPanel frame1 = drawkline.getChartPanel(); JFrame frame=new JFrame("Java數據統計圖"); frame.add(frame1); frame.setBounds(50, 50, 800, 600); frame.setVisible(true); } } } }
二、根據得出的股票價格數據繪圖:Java繪圖我是採用了JFreeChart,它是JAVA平臺上的一個開放的圖表繪製類庫。須要導入三個包:jfreechart.jar、jcommon.jar、gnujaxp.jar,這些包能夠去http://mvnrepository.com/上下載,版本能夠挑選下載量最多的。而後根據獲得的股票數據建立兩個繪圖類DrawKlineChart和DrawLineChart來分別繪製K線圖和分時圖,如下爲繪圖代碼:
//繪製K線圖 package DrawMethod; import java.awt.Color; import java.awt.Font; import java.text.SimpleDateFormat; import java.util.ArrayList; import Main.Klineobject; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.CandlestickRenderer; import org.jfree.data.time.*; import org.jfree.data.time.ohlc.OHLCSeries; import org.jfree.data.time.ohlc.OHLCSeriesCollection; import org.jfree.data.xy.*; public class DrawKlineChart { ChartPanel frame1; /*public void showObject(ArrayList<Klineobject> object,int dayCount){ for(int i=0;i<object.size();i++){ System.out.println("第"+i/dayCount+"支股票第 "+i%dayCount+" 天: "+" 開盤價: "+object.get(i).getStartPrice()+" 收盤價:"+object.get(i).getClosePrice()+" 最高價:"+object.get(i).getMaxPrice()+" 最低價:"+object.get(i).getMinPrice()); } }*/ public DrawKlineChart(ArrayList<Klineobject> object,int dayCount){ //showObject(object,dayCount); //將數據集進行輸入 OHLCDataset xydataset = createDataset(object, dayCount); //用獲得的數據集開始畫圖 CandlestickRenderer candlestickRender=new CandlestickRenderer();// 設置K線圖的畫圖器, candlestickRender.setUseOutlinePaint(true); // 設置是否使用自定義的邊框線,程序自帶的邊框線的顏色不符合中國股票市場的習慣 candlestickRender.setAutoWidthMethod(CandlestickRenderer.WIDTHMETHOD_AVERAGE);// 設置如何對K線圖的寬度進行設定 candlestickRender.setAutoWidthGap(0.001);// 設置各個K線圖之間的間隔 candlestickRender.setUpPaint(Color.RED);// 設置股票上漲的K線圖顏色 candlestickRender.setDownPaint(Color.GREEN);// 設置股票下跌的K線圖顏色 DateAxis x1Axis=new DateAxis("時間"); NumberAxis y1Axis=new NumberAxis("交易價格"); XYPlot plot1=new XYPlot(xydataset,x1Axis,y1Axis,candlestickRender); JFreeChart jfreechart = new JFreeChart("股票交易模擬K線圖", plot1); XYPlot xyplot = (XYPlot) jfreechart.getPlot(); frame1=new ChartPanel(jfreechart,true); //設置圖表的字體格式 DateAxis dateaxis = (DateAxis) xyplot.getDomainAxis(); dateaxis.setLabelFont(new Font("黑體",Font.BOLD,14)); //水平底部標題 dateaxis.setTickLabelFont(new Font("宋體",Font.BOLD,12)); //垂直標題 ValueAxis rangeAxis=xyplot.getRangeAxis();//獲取柱狀 rangeAxis.setLabelFont(new Font("黑體",Font.BOLD,15)); jfreechart.getLegend().setItemFont(new Font("黑體", Font.BOLD, 15)); jfreechart.getTitle().setFont(new Font("宋體",Font.BOLD,20));//設置標題字體 } private OHLCDataset createDataset(ArrayList<Klineobject> object, int dayCount) { OHLCSeriesCollection seriescollection = new OHLCSeriesCollection (); OHLCSeries series = new OHLCSeries(""); for(int i=0;i<object.size();i++){ if(i%dayCount==0){ int num = i/dayCount+1; String name="第"+num+"支股票"; series = new OHLCSeries(name); } Day day = new Day(i%dayCount+16,11,2017); series.add(day, object.get(i).getStartPrice(), object.get(i).getMaxPrice(), object.get(i).getMinPrice(), object.get(i).getClosePrice());//對應於開、高、低、收 //對同一支股票計算到最後一自然後加入collection中 if(i%dayCount==dayCount-1){ seriescollection.addSeries(series); } } return seriescollection; } public ChartPanel getChartPanel(){ return frame1; } }
//繪製分時圖 package DrawMethod; import java.awt.Font; import java.text.SimpleDateFormat; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.XYPlot; import org.jfree.data.time.*; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.xy.*; public class DrawLineChart { ChartPanel frame1; public DrawLineChart(double[][] prices){ XYDataset xydataset = createDataset(prices); JFreeChart jfreechart = ChartFactory.createTimeSeriesChart("股票交易模擬分時圖", "時間", "交易價格", xydataset,true,true,true); XYPlot xyplot = (XYPlot) jfreechart.getPlot(); frame1=new ChartPanel(jfreechart,true); DateAxis dateaxis = (DateAxis) xyplot.getDomainAxis(); dateaxis.setLabelFont(new Font("黑體",Font.BOLD,14)); //水平底部標題 dateaxis.setTickLabelFont(new Font("宋體",Font.BOLD,12)); //垂直標題 ValueAxis rangeAxis=xyplot.getRangeAxis();//獲取柱狀 rangeAxis.setLabelFont(new Font("黑體",Font.BOLD,15)); jfreechart.getLegend().setItemFont(new Font("黑體", Font.BOLD, 15)); jfreechart.getTitle().setFont(new Font("宋體",Font.BOLD,20));//設置標題字體 } private XYDataset createDataset(double[][] prices) { TimeSeriesCollection timeseriescollection = new TimeSeriesCollection(); for(int i=0;i<prices.length;i++){ int num = i+1; String name="第"+num+"支股票"; TimeSeries timeseries = new TimeSeries(name); Day day;Hour hour; for(int j=0;j<prices[0].length;j++){ day = new Day(j/120+16,11,2017); if(j%120>=0&&j%120<60){ hour = new Hour(9,day); } else{ hour = new Hour(10,day); } timeseries.add(new Minute(j%60,hour), prices[i][j]);; } timeseriescollection.addSeries(timeseries); } return timeseriescollection; } public ChartPanel getChartPanel(){ return frame1; } }
三、以上已經將整個觀察者模式的框架搭建完成,包括數據分析圖的繪製,所以能夠在這個基礎上編寫圖形用戶界面來使用該觀察者模式。所以編寫Application類來做爲程序的入口,用戶能夠經過點擊對應的分時圖或K線圖按鈕來查看相應的分析圖:
package Main; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Random; import javax.swing.*; import Observer.KlineFigure; import Observer.TimeFigure; import Subject.StockDealSubject; import Subject.Subject; public class Application extends JFrame implements ActionListener{ double[][] prices; JButton TimeFigureBtn = new JButton("查看分時圖"); JButton KlineFigureBtn = new JButton("查看K線圖"); public Application(String title){ super(title); //隨機產生4支股票並隨機產生1200個隨機數來模擬10天股票價格變化,天天交易2小時; prices = getPrice(120,2); StockDealSubject stockSubject = new StockDealSubject(); TimeFigure timeFigure = new TimeFigure(stockSubject); KlineFigure klineFigure = new KlineFigure(stockSubject); Container c = getContentPane(); c.setLayout(new FlowLayout(FlowLayout.CENTER)); TimeFigureBtn.setPreferredSize(new Dimension(100,40)); KlineFigureBtn.setPreferredSize(new Dimension(100,40)); c.add(TimeFigureBtn); c.add(KlineFigureBtn); TimeFigureBtn.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { stockSubject.setPriceandType(prices, "TimeFigure"); stockSubject.notifyObservers(); } }); KlineFigureBtn.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { stockSubject.setPriceandType(prices, "KlineFigure"); stockSubject.notifyObservers(); } }); setSize(500,150); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public double[][] getPrice(int size,int n){ double[][] price = new double[n][size]; Random ran = new Random(); for(int i=0;i<n;i++){ for(int j=0;j<size;j++){ price[i][j]=j==0?ran.nextDouble()*20-10+100:ran.nextDouble()*1-0.5+price[i][j-1]; } } return price; } public static void main(String[] args) { // TODO Auto-generated method stub Application application = new Application("分時圖&K線圖"); application.setVisible(true); } @Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub } }
package Main; public class Klineobject { double startPrice; double closePrice; double maxPrice; double minPrice; public double getStartPrice(){ return startPrice; } public double getClosePrice(){ return closePrice; } public double getMaxPrice(){ return maxPrice; } public double getMinPrice(){ return minPrice; } public Klineobject(double startPrice,double closePrice,double maxPrice,double minPrice){ this.startPrice = startPrice; this.closePrice = closePrice; this.maxPrice = maxPrice; this.minPrice = minPrice; } }
3、實驗效果截圖:
①運行程序,查看4支股票在一天內的股價變化圖,則經過getPrice(120,4)來獲取最初的股價數據,如下爲截圖(因爲我定義的隨機數生成是在Application中的,故須要經過從新運行程序來得到多組結果):
②運行程序,查看2只股票10天內的K線圖,則經過getPrice(1200,2)來獲取最初的股價數據,如下爲截圖(因爲我定義的隨機數生成是在Application中的,故須要經過從新運行程序來得到多組結果):
以上即爲本次對觀察者模式的學習與應用,感受蠻有趣的哈哈
轉載請註明出處,謝謝~