這學期學了各種軟件設計模式,老師要求我們利用抽象工廠、組合、迭代器、觀察者等設計模式實現一個圖形編輯系統。雖然基本功能實現了,但還是不確定是否是真的符合各種設計模式的要求
目錄
1 實現過程
1.1 採用抽象工廠設計模式創建圖形對象
抽象工廠模式提供了一個創建一系列相應或相互依賴的接口,在這個項目中,要創建的就是各種圖形對象,當有多個類需要創建實例的時候,就可用抽象工廠模式。
- 首先要確定需要創建哪些類的實例。由於需要畫出直線、實心圓圈符號、文本字符串、方框,這四個都屬於圖形,那麼根據面向對象的方法,可以把這四個圖形抽象出一個圖形類Graphical作爲這四個圖形的父類,每個圖形都要實現的方法有繪製圖形,那麼就可以定義一個繪製方法,每個具體的圖形類實現具體的內容
- 確定了要創建哪些圖形對象後,就可以設計抽象工廠從而通過工廠創建實例對象。創建一個抽象工廠類AbstractFactory,在其中爲每一個產品類定義相應的創建方法。
- 最後只需要再創建AbstractFactory類的子類GraphicalFactory,在其中實現抽象工廠類定義的各個創建方法,每次調用相應的方法就會返回一個對應類型的對象。
1.2 採用組合設計模式設計直線、符號、字符串、方框及複合圖形
組合模式將對象組合成樹形結構,表示“部分—整體”的層次結構。由容器構件和葉子構件組成,可以理解成容器構建就是樹結構中的非葉子結點,葉子構件就是葉子節點。一個容器對象中放多種不同的葉子對象或容器對象。爲了可以一致使用組合結構和單個對象,容器構件和葉子構件要繼承同一個父類作爲抽象構件,我理解的就是有點面向接口編程的感覺
- 由於可能存在圖形組合在一起使用形成複合圖形的情況,所以要用組合設計模式,那麼就要再額外增加一個圖形類的子類Graphic,使其作爲容器構件,其中需要一個集合來存儲這個容器構件中的子組件。另外4個圖形類作爲葉子構件,他們的抽象父類Graphical作爲抽象構件。
- 增加了Grapics類之後需要在之前的抽象設計工廠中增加新的方法來創建Grapics容器構建的實例對象。
- 使用組合設計模式需要維護對各個子圖形的訪問,那麼就要定義新的方法實現對子組件的管理,例如添加、刪除組件
1.3 採用迭代器設計模式訪問複合圖形中的各個子圖形
迭代器模式提供了一種方法順序訪問一個集合對象中的各個元素,而不暴露該對象的內部表示,簡單理解就是把對一個集合的訪問、遍歷分離到一個迭代器對象中來實現
- 存在圖形組合在一起形成複合圖形的情況,那麼就有可能會對複合圖形進行操作,所以可以用迭代器設計模式來對組合圖形中的各個子圖形進行操作。那麼各個容器構件對象作爲聚合類,爲迭代器提供需要遍歷的集合
- 增加了迭代器之後需要在之前的Graphical類中增加新的方法來創建相應的迭代器
- 創建一個抽象迭代器類GraphicalIterator,在其中爲定義管理遍歷方式的方法
- 然後創建GraphicalIterator類的子類ConcreteIteratorImpl,在其中實現抽象迭代器類定義的各個方法,同時因爲迭代器不暴露對象的內部表示,所以需要有一個集合存儲從遍歷對象得到的元素集合
1.4 採用觀察者設計模式實現圖形的拖動、放大縮小
觀察者模式定義了對象之間的一種一對多的依賴關係,當一個對象狀態改變時,其相關依賴的對象都得到通知並自動更新,依賴的對象也叫做觀察者
- 因爲可能會同時處理幾個圖形對象,那麼爲了方便統一操作,可以使用觀察者模式。將這些圖形對象作爲觀察者,當要對圖形對象進行操作時,只需要接收通知然後做出反應就可以了
- 使用觀察者模式需要在之前的Graphical類中增加新的方法來接收目標類的更新通知
- 首先創建一個抽象目標類GraphicalSubject,在其中定義管理觀察者的方法
- 然後創建GraphicalSubject類的子類GraphicalSubjectImpl,在其中實現抽象目標類定義的各個方法,同時需要有一個集合存儲所有的觀察者,而且不能亂通知,所以需要一個表示狀態的屬性,只有在更新狀態下才能進行通知
2 實現細節
- 無法畫圖:
畫圖需要使用java.awt.Graphics類作爲畫筆來使用,需要在面板類中重寫paintComponent(Graphics g)方法,在其中進行畫圖的操作。做好這一步之後就弄好了畫圖的準備工作,如果要讓它能夠正常畫出來,還需要在窗口類中添加重寫了paintComponent(Graphics g)方法的面板。至此就可以畫圖了 - 無法調節面板大小:
當面板添加到窗口中後,面板大小是固定的無法調整,需要調整窗口的佈局,不能使用默認佈局,否則面板對象使用了調整大小的方法也不會起作用。所以將窗口的佈局設爲null就可以自由調整面板的大小和位置了 - 鼠標移動的位置和畫出來的圖不在同一位置:
由於鼠標移動的座標監聽的是在窗口上的座標,而畫出的圖形是在面板上的,如果面板和窗口沒有重合,那麼畫出來的圖的位置就會有偏差。所以將面板對象調用setBounds()方法將其的大小、位置與窗口重合即可 - 創建功能按鈕時重複、相似的代碼過多:
由於畫不同的圖形需要先點擊對應的按鈕才能畫,所以需要創建很多按鈕,但是這些點擊按鈕的功能很類似,都是指明接下來要幹什麼,如果一個一個的創建會很麻煩,代碼看起來很長卻基本都是重複的內容。所以可以把這些按鈕放在一個按鈕數組裏,再增加一個按鈕名稱的數組,創建時只需要用一個循環通過不同的下標創建就可以了 - 選擇畫圖類型代碼冗長:
最開始爲每一個類型的圖形都增加了一個標記,當前要畫的是什麼類型就根據對應的標記來選擇,由於不同類型的圖形畫圖的過程也不一樣,全部堆在paintComponent(Graphics g)方法中來畫就會使代碼很長很複雜。由於基礎圖形就是那4種,所以可以把各自的畫圖過程重寫在每個圖形類中的draw()方法中,爲了能夠使draw()方法能夠畫圖,需要把paintComponent(Graphics g)中的g作爲參數傳遞給draw()方法中,最後變成draw(Graphics g),不同圖形的畫法就在draw裏實現,由於不管當前要畫的是什麼圖形,都是繼承了Graphical類的,都存在draw(Graphics g)方法,只是不同類的實現方法不同,所以在paintComponent(Graphics g)中只需要直接調用當前圖形對象的draw(Graphics g)方法就可以了。 - 如何畫圖:
要確定一個圖形的位置、大小,需要得到一個起點座標和一個終點座標,可以通過按下鼠標得到鼠標的座標,使其作爲起點座標;鬆開鼠標得到鼠標新的座標,使其作爲終點座標。得到這兩個座標之後就能畫出所有的基本圖形:兩點確定一條直線、兩座標相減得到矩形的長和寬、兩座標之間的距離確定半徑 - 圖形移動、放大後原來位置上還存在圖形:
因爲畫板沒有更新,所以原來畫出來的東西還會在,如果要更新畫板內容的話,需要在paintComponent(Graphics g)方法中先執行super.paintComponent(Graphics g)將畫板上的內容全部清除再重新畫。那麼就會有一個新問題,原本沒有移動或改變大小的圖形也會在更新的時候消失。那麼就需要一個列表來存儲所有已經創建出來的圖形對象,每一個圖形對象中都記錄着起點和終點,有了這些信息之後,即使畫板更新後,也能通過遍歷所有的圖形對象根據其中的座標重新畫出圖形。 - 如何選中要進行操作的圖形:
選中圖形其實上就是選中對應的圖形對象。由於圖形可以組合並且可能是不規則的,不好判定是否選中了該圖形對象,所以規定無論是什麼圖形都以它的起始座標和終點座標圍成的矩形作爲該圖形對象的選中範圍。同時由於可能對多個圖形同時進行操作,那麼就需要使用到觀察者模式,將被選中的圖形都作爲觀察者。當鼠標按下時,得到鼠標的座標,然後遍歷所有的圖形對象,若該點在圖形對象的選中範圍內就將其作爲觀察者放入目標對象中的觀察者集合中,之後就只需要同時對這些觀察者就行操作就好了 - 如何移動圖形:
移動圖形最根本的就是改變原有圖形對象的起點和終點座標,那麼要怎麼改變座標就可以通過記錄鼠標移動的距離和位置,通過起點和終點座標同時加減鼠標移動的距離就可以得到移動之後的新座標。此處鼠標移動的距離通過鼠標終點的座標減去鼠標起點的座標得到,可能爲正也可能爲負,正的就是往右、下方向移動,負的就是往左、上方向移動 - 如何放大圖形:
和移動圖形類似,放大圖形是改變原有圖形對象的終點座標,通過記錄鼠標移動的距離和位置,然後終點座標同時加減鼠標移動的距離就可以得到移動之後的新座標。此處鼠標移動的距離也可能爲正也可能爲負,正的就是放大,負的就是縮小。
3 完整代碼
public abstract class Graphical { //圖形抽象構件類
int x1 ; //起點座標
int y1 ;
int x2 ; //終點座標
int y2 ;
public int getX1() {
return x1;
}
public void setX1(int x1) { this.x1 = x1; }
public int getY1() {
return y1;
}
public void setY1(int y1) {
this.y1 = y1;
}
public int getX2() {
return x2;
}
public void setX2(int x2) {
this.x2 = x2;
}
public int getY2() {
return y2;
}
public void setY2(int y2) {
this.y2 = y2;
}
//添加組件
public void add(Graphical graphical) {}
//刪除組件
public void remove(Graphical graphical) {}
//畫圖形
public void draw(Graphics g) {}
//觀察者更新數據
public void update(int movedx, int movedy, int function) { //更新新的座標
if(function == 1) { //移動
x1 += movedx ; //移動的話起點、終點都要改變
x2 += movedx ;
y1 += movedy ;
y2 += movedy ;
}
else { //放大縮小
x2 += movedx ; //放大的話起點要做支點,改變終點
y2 += movedy ;
}
}
}
public class Line extends Graphical { //直線類
public void draw(Graphics g) { //畫直線
g.drawLine(x1, y1, x2, y2);
}
}
public class Rectangle extends Graphical{ //矩形框類
public void draw(Graphics g) { //畫矩形
int height = Math.abs(y1 - y2) ; //長,要取絕對值保證大於等於0
int width = Math.abs(x1 - x2) ; //寬
g.drawRect(x1, y1, width, height);
}
}
public class Text extends Graphical { //文本字符串類
public void draw(Graphics g) { //畫字符串
g.drawString("雙擊輸入內容", x1, y1);
}
}
public class Circle extends Graphical { //實心圓圈類
public void draw(Graphics g) { //畫圓
int r = Math.abs(x1 - x2) ; //半徑
g.fillOval(x1, y1, r, r);
}
}
public class Graphic extends Graphical { //圖形容器構件類
//存儲結點的集合,可放各類圖形
private List<Graphical> graphicalList = new ArrayList<Graphical>();
private int flag ; //記錄是什麼類型的圖形
public int getFlag() { return flag; }
public void setFlag(int flag) {
this.flag = flag;
}
public Graphic() {
x1 = -99999 ; //x1爲大的
x2 = 99999 ; //x2爲小的
y1 = -99999 ; //大
y2 = 99999 ; //小
}
//添加子結點
public void add(Graphical graphical) {
graphicalList.add(graphical);
}
//刪除子結點
public void remove(Graphical graphical) {
graphicalList.remove(graphical);
}
//判斷是否選中了該圖形,用於移動和放大
public boolean isChosed(int x, int y) {
if(x<=x1 && x>=x2 && y<=y1 && y>=y2) //若鼠標點擊的位置在圖形範圍內就可以選中圖形
return true ;
else
return false ;
}
//修改容器的大小
public void setSize(int x1, int y1, int x2, int y2) {
//如果容器中的葉子構件的大小比原本容器的範圍大就更新範圍
int maxX = x1>x2 ? x1:x2 ;
int minX = x1<=x2 ? x1:x2 ;
int maxY = y1>y2 ? y1:y2 ;
int minY = y1<=y2 ? y1:y2 ;
if(this.x1 < maxX)
this.x1 = maxX;
if(this.x2 > minX)
this.x2 = minX ;
if(this.y1 < maxY)
this.y1 = maxY ;
if(this.y2 > minY)
this.y2 = minY ;
}
//接收目標的通知後,作爲觀察者的圖形更新座標
public void update(int movedx, int movedy, int function) {//重寫update方法
super.update(movedx, movedy, function); //整個容器構件的範圍要更新
GraphicalIteratorImpl iterator = createIterator() ; //創建迭代器
while (iterator.isDone()) { //按照迭代器的方法遍歷容器中的所有圖形
Graphical graphical = iterator.next(); //當前圖形
graphical.update(movedx, movedy, function); //對容器構件中所有子構件進行座標更新
}
}
//創建迭代器
public GraphicalIteratorImpl createIterator() {
return new GraphicalIteratorImpl(graphicalList) ; //類似工廠
}
}
public abstract class AbstractFactory {
//創建一個直線對象
public Line createLine() {
return null ;
};
//創建一個圓圈對象
public Circle createCircle() {
return null ;
};
//創建一個文本字符串對象
public Text createText() {
return null ;
};
//創建一個矩形框對象
public Rectangle createRectangle() {
return null ;
};
//創建一個圖形容器對象
public Graphic createGraphics() {
return null ;
};
}
public class GraphicalFactory extends AbstractFactory {
//創建一個直線對象
public Line createLine() {
return new Line();
}
//創建一個圓圈對象
public Circle createCircle() {
return new Circle();
}
//創建一個文本字符串對象
public Text createText() {
return new Text();
}
//創建一個矩形框對象
public Rectangle createRectangle() {
return new Rectangle();
}
//創建一個圖形容器對象
public Graphic createGraphics() {
return new Graphic();
}
}
public interface GraphicalIterator { //抽象迭代器
//判斷遍歷是否結束
public boolean isDone();
//獲得下一個元素
public Graphical next();
}
public class GraphicalIteratorImpl implements GraphicalIterator {//具體迭代器類
private List<Graphical> traversalList; //需要遍歷的圖形集合
private Graphical currentGraphical ; //當前元素
private int position = 0; //遍歷位置
public GraphicalIteratorImpl(List<Graphical> traversalList) {
this.traversalList = traversalList ;
}
//判斷是否遍歷結完
public boolean isDone() {
return position < traversalList.size();
}
//獲取下一個元素
public Graphical next() {
currentGraphical = traversalList.get(position) ;
position++ ;
return currentGraphical ;
}
}
public interface GraphicalSubject { //目標接口
//添加觀察者
public void attach(Graphical graphical) ;
//刪除觀察者
public void delach(Graphical graphical) ;
//通知觀察者
public void Notify() ;
}
public class GraphicalSubjectImpl implements GraphicalSubject{ //具體目標類
private List<Graphical> observerList = new ArrayList<Graphical>(); //觀察者集合
private boolean state = false ; //當前狀態
private boolean hasNumber = false ; //記錄是否有觀察者
private int function ; //記錄是移動還是放大
private int startX ; //初始點擊的位置
private int startY ;
private int movedX ; //鼠標移動的距離
private int movedY ;
public GraphicalSubjectImpl(int function) {
this.function = function ;
}
public boolean hasNumber() {
return hasNumber;
}
public void setNumber(boolean number) {
hasNumber = number;
}
public int getMovedX() {
return movedX;
}
public void setMovedX(int movedX) {
this.movedX = movedX;
}
public int getMovedY() {
return movedY;
}
public void setMovedY(int movedY) {
this.movedY = movedY;
}
public int getStartX() {
return startX;
}
public void setStartX(int startX) {
this.startX = startX;
}
public int getStartY() {
return startY;
}
public void setStartY(int startY) {
this.startY = startY;
}
//添加觀察者
public void attach(Graphical graphical) {
observerList.add(graphical) ;
}
//刪除觀察者
public void delach(Graphical graphical) {
if(observerList.contains(graphical)) //存在才刪除
observerList.remove(graphical) ;
}
//通知觀察者
public void Notify() {
if(state == false) //不是更新狀態不能通知觀察者
return;
GraphicalIteratorImpl iterator = this.createIterator() ; //創建迭代器
while (iterator.isDone()){ //按照迭代器的方法遍歷所有觀察者
Graphical graphical = iterator.next(); //當前圖形
graphical.update(movedX, movedY, function); //更新座標
}
this.clearState(); //通知完之後要結束更新狀態
}
//設置更新狀態
public void setState(){
state = true ;
}
//清除更新狀態
public void clearState() {
state = false ;
}
//創建迭代器
public GraphicalIteratorImpl createIterator() { //類似工廠
return new GraphicalIteratorImpl(observerList) ;
}
}
public class Main {
public static void main(String[] args) {
Window window = new Window();
}
}
public class Window extends JFrame {
DrawPanel drawPanel ; //畫圖面板
public Window() {
drawPanel = new DrawPanel() ;
add(drawPanel) ; //只有將面板添加到窗口才能觸發畫圖函數
//面板大小要和窗口一樣否則畫出來座標不一樣
drawPanel.setBounds(0, 0, 800, 600);
this.addMouseListener(drawPanel); //監聽鼠標事件從而得到起始、終點座標
this.addMouseMotionListener(drawPanel); //監聽鼠標拖動時的座標形成畫圖時的動畫
setLayout(null); //畫板要能調節大小,則窗口不能使用默認排版方式
setBounds(500, 200, 800, 600);
setVisible(true);
validate();
setDefaultCloseOperation(Window.EXIT_ON_CLOSE);
}
}
public class DrawPanel extends JPanel implements ActionListener, MouseListener, MouseMotionListener {
ArrayList<Graphic> graphicList = new ArrayList<Graphic>() ; //存儲每一個已經建立的圖形
GraphicalSubjectImpl subject ; //觀察者模式的目標類
JButton[] buttons = new JButton[11] ; //按鈕功能類似,用一個數組存放就可以了
String[] buttonName = {"移動","放大","清空","直線","圓圈","矩形框","文本","數字直線","字符串符號",
"標誌線字符串", "內虛框矩形"} ; //按鈕名字
GraphicalFactory graphicalFactory = new GraphicalFactory() ; //圖形工廠
Graphical graphical ; //當前要處理的臨時圖形
Graphic graphic ; //當前要處理的臨時容器構件
int flag ; //標記當前畫什麼
boolean moveOrScale ; //移動或放大
public DrawPanel(){
flag = -1 ; //初始化要畫的類型
moveOrScale = false ; //初始化不執行移動或放大
for(int i=0; i<11; i++) { //循環創建按鈕
buttons[i] = new JButton(buttonName[i]) ;
add(buttons[i]) ; //將按鈕添加到面板中才能顯示
buttons[i].addActionListener(this); //爲每個按鈕都添加點擊事件
}
setVisible(true);
}
public void paintComponent(Graphics g) { //畫圖方法
super.paintComponent(g); //清空之前的內容再重新畫
for(int i=0; i<graphicList.size(); i++) { //將所有圖形重畫
graphic = graphicList.get(i) ; //當前容器構件
flag = graphic.getFlag() ; //根據類型畫相應的圖形
GraphicalIteratorImpl iterator = graphic.createIterator() ;//迭代器
while (iterator.isDone()) { //按照迭代器的方法遍歷容器中的所有圖形
graphical = iterator.next(); //當前圖形
if(graphical != null) { //只有容器中的子構件不爲空纔有東西可以畫
g.setColor(Color.black); //設置畫的顏色
//畫出一個圖形要檢測更新容器構件的範圍
graphic.setSize(graphical.getX1(), graphical.getY1(),
graphical.getX2(), graphical.getY2());
graphical.draw(g); //根據對應圖形的畫法畫出圖形
switch (flag) { //複合圖形
case 5 : { //畫有數字的直線
int x = (graphical.getX1()+graphical.getX2()) / 2 ;
int y = (graphical.getY1()+graphical.getY2()) / 2 ;
g.drawString("200", x, y);
//字符串是在直線上的則不需要再次判斷範圍
break;
}
case 6 : { //畫有字符串的符號
int x = (graphical.getX1()+graphical.getX2()) / 2 ;
int y = (graphical.getY1()+graphical.getY2()) / 2 ;
g.setColor(Color.red); //畫紅色的圓
g.fillOval(x, y, 30, 30); //畫圓
g.setColor(Color.black);
for(int j=0; j<10; j++) { //畫虛線
g.drawLine(x, y,
x-5, y+5); //畫直線
x -= 10 ; //每次畫直線都要間隔一定距離就可以形成虛線的效果
y += 10 ;
}
//畫了新的內容出來需要檢測更新容器的範圍大小
graphic.setSize(x, y, x, y);
g.drawString("靜61-127C", x-10, y+10); //畫字符串
graphic.setSize(x-10, y+10, x-10, y+10);
break;
}
case 7 : { //畫有標誌線的字符串
g.drawLine(graphical.getX2(), graphical.getY2(),
graphical.getX2()+150,graphical.getY2());//畫水平線
graphic.setSize(graphical.getX2(), graphical.getY2(),
graphical.getX2()+150,graphical.getY2());
g.drawString("雙擊輸入標註內容", graphical.getX2()+5,
graphical.getY2()-5); //畫字符串
break;
}
case 8 : { //有內虛框的方框
int x1 = graphical.getX1() ;
int y1 = graphical.getY1() + 5;
int x2 = graphical.getX2() ;
int y2 = graphical.getY2() ;
while(y1 < y2-10) { //畫垂直的虛線
g.drawLine(x1+5, y1,
x1+5, y1+5); //畫直線
g.drawLine(x2-5, y1,
x2-5, y1+5);
y1 += 10 ;
}
y1 = graphical.getY1() ;
x1 += 5 ;
while(x1 < x2-10) { //畫水平的虛線
g.drawLine(x1, y1+5,
x1+5, y1+5); //畫直線
g.drawLine(x1, y2-5,
x1+5, y2-5);
x1 += 10 ;
}
break;
}
}
}
}
}
}
public void actionPerformed(ActionEvent e) { //先選則要幹什麼再創建相應的對象
//使用getActionCommand()獲得按鈕上的文字
if(e.getActionCommand().equals("移動") || e.getActionCommand().equals("放大")) { //移動圖形
if(e.getActionCommand().equals("移動")) //移動
subject = new GraphicalSubjectImpl(1) ; //創建一個目標類
else //放大
subject = new GraphicalSubjectImpl(2) ; //創建一個目標類
moveOrScale = true ;
}
else if(e.getActionCommand().equals("清空") ) {
graphicList.clear(); //將已經生成的所有圖形對象都刪除
this.repaint(); //重畫,由於所有圖形對象都沒有了達到清空的效果
}
else { //畫圖形
//不管畫什麼圖形都放在一個新的容器構件中,之後要在其中加子構件比較方便
//每次畫完一個之後就要點擊一次按鈕纔會創建新的對象,否則一直都是不斷對相同的對象重寫畫圖
graphic = graphicalFactory.createGraphics() ; //創建新容器構件
graphicList.add(graphic) ; //將該容器構件放入圖形列表中
switch (e.getActionCommand()) { //用switch效率比if高一些
case "直線" : {
graphical = graphicalFactory.createLine(); //創建一個直線作爲葉子構件
graphic.setFlag(1);
break;
}
case "矩形框" : {
graphical = graphicalFactory.createRectangle();
graphic.setFlag(2);
break;
}
case "文本" : {
graphical = graphicalFactory.createText() ;
graphic.setFlag(3);
break;
}
case "圓圈" : {
graphical = graphicalFactory.createCircle() ;
graphic.setFlag(4);
break;
}
case "數字直線" : {
graphical = graphicalFactory.createLine() ;
graphic.setFlag(5);
break;
}
case "字符串符號" : {
graphical = graphicalFactory.createLine() ;
graphic.setFlag(6);
break;
}
case "標誌線字符串" : {
graphical = graphicalFactory.createLine() ;
graphic.setFlag(7);
break;
}
case "內虛框矩形" : {
graphical = graphicalFactory.createRectangle() ;
graphic.setFlag(8);
break;
}
}
graphic.add(graphical); //將葉子構件加入到新創建的容器構件中
}
}
//鼠標按下得到起始位置,用於畫圖或者移動放大
public void mousePressed(MouseEvent e) {
if(moveOrScale) { //移動
//將點擊座標在圖形範圍內的所有容器構件作爲觀察者中
for(int i=0; i<graphicList.size(); i++) { //遍歷圖形列表
graphic = graphicList.get(i) ; //當前容器構建
//起點在容器構件的範圍內才能移動該容器中的圖形
if(graphic.isChosed(e.getX(), e.getY())) { //判斷該點是否在容器構件範圍內
subject.attach(graphic); //容器構件作爲觀察者
subject.setNumber(true); //標記存在觀察者
}
}
if(subject.hasNumber()) { //存在觀察者就繼續操作
subject.setStartX(e.getX()); //設置移動的初始位置
subject.setStartY(e.getY());
}
else //不存在觀察者就不用繼續操作了
moveOrScale = false ;
}
else { //畫圖
if(graphical != null) { //只有當前有對象才能存起始位置
graphical.setX1(e.getX());
graphical.setY1(e.getY());
System.out.println(graphical.getX1()+ " "+ graphical.getY1());
}
}
}
//鼠標擡起得到終止位置
public void mouseReleased(MouseEvent e) {
if(moveOrScale) { //移動或放大
//獲得鼠標移動的距離,正負都是有可能的
//例如放大功能中得到正數爲放大,負數爲縮小
subject.setMovedX(e.getX() - subject.getStartX());
subject.setMovedY(e.getY() - subject.getStartY());
subject.setState(); //修改爲更新狀態,只有更新狀態下才能進行Notify方法
subject.Notify(); //觀察者通知範圍內的所有圖形移動
subject = null ; //執行完後要捨去該目標對象,否則裏面還會存在之前的內容
this.repaint(); //容器構件中圖新座標更新後就重新畫
moveOrScale = false ; //結束移動或放大
}
else { //畫圖
if(graphical != null) {
graphical.setX2(e.getX()); //終點座標
graphical.setY2(e.getY());
System.out.println(graphical.getX2()+ " "+ graphical.getY2());
this.repaint(); //獲得起點終點後就可以畫圖了
}
}
}
//拖動鼠標時不斷畫圖形,形成動畫效果
public void mouseDragged(MouseEvent e) {
if(!moveOrScale) { //畫圖過程
if(graphical != null) {
graphical.setX2(e.getX()); //將拖動鼠標時得到的座標作爲暫時的終點座標
graphical.setY2(e.getY());
System.out.println(graphical.getX2()+ " "+ graphical.getY2());
this.repaint(); //獲得起點終點後就可以畫圖了
}
}
}
//一下均爲未使用到的方法
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
}
4 總結
- 要思考要實現的這些功能以什麼樣的設計模式來做比較合理。比如圖形可能會組合在一起使用,那麼用組合設計模式來創建複合圖形就比較容易對其進行各種操作。如果經驗比較少的話可能一下子不容易找出適合的設計模式,但是可以先確定一個大概的方向,然後先以自己能夠實現的方式來做,完成之後再來分析這段代碼合不合理,哪裏有可以改進的地方,思考什麼樣的設計模式能夠優化代碼。找到合適的設計模式之後就對原本的代碼進行改進。這個過程雖然不符合正規的設計方式,但是我認爲對於新手來說這是學習、理解一個軟件設計模式最好的方法
- 很多時候是因爲我們的經驗不足,做過的項目太少,所以很難一下子就能輕鬆明白什麼地方應該用什麼樣的設計模式更合理,所以在打代碼的過程中,一個功能用了幾種方式後才選出了真正適合的設計模式。所以只有多做項目,擴大項目規模,不斷髮現錯誤和不合理的地方,才能對這些設計模式有更加深刻的印象和理解
- 第一次用這些設計模式,可能不全對,之後經驗多了可能會發現其中的一些問題吧
- 這次做實驗,也是我第一次好好把不同類型的文件放在不同的包裏,雖然還不太確定要把哪個文件放在哪個包裏,但是這樣簡單的劃分一下還是很有幫助的,找某個內容時就會方便很多