这学期学了各种软件设计模式,老师要求我们利用抽象工厂、组合、迭代器、观察者等设计模式实现一个图形编辑系统。虽然基本功能实现了,但还是不确定是否是真的符合各种设计模式的要求
目录
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 总结
- 要思考要实现的这些功能以什么样的设计模式来做比较合理。比如图形可能会组合在一起使用,那么用组合设计模式来创建复合图形就比较容易对其进行各种操作。如果经验比较少的话可能一下子不容易找出适合的设计模式,但是可以先确定一个大概的方向,然后先以自己能够实现的方式来做,完成之后再来分析这段代码合不合理,哪里有可以改进的地方,思考什么样的设计模式能够优化代码。找到合适的设计模式之后就对原本的代码进行改进。这个过程虽然不符合正规的设计方式,但是我认为对于新手来说这是学习、理解一个软件设计模式最好的方法
- 很多时候是因为我们的经验不足,做过的项目太少,所以很难一下子就能轻松明白什么地方应该用什么样的设计模式更合理,所以在打代码的过程中,一个功能用了几种方式后才选出了真正适合的设计模式。所以只有多做项目,扩大项目规模,不断发现错误和不合理的地方,才能对这些设计模式有更加深刻的印象和理解
- 第一次用这些设计模式,可能不全对,之后经验多了可能会发现其中的一些问题吧
- 这次做实验,也是我第一次好好把不同类型的文件放在不同的包里,虽然还不太确定要把哪个文件放在哪个包里,但是这样简单的划分一下还是很有帮助的,找某个内容时就会方便很多