在学习了《软件构造》这门课程,尤其是做完lab3之后,我对于ADT的设计又有了新的理解。在原来我写过关于lab2中的棋类问题的ADT设计,但是更多的是从抽象的角度去理解和设计ADT,关注的重点是如何将现实中的事物抽象成为计算机中可实现的抽象数据。而在完成了lab3之后,我不仅要关注如何抽象一个现实事物,更多的还要关注设计的方法、设计的思路、设计的技巧。
在lab3中,首当其冲的就是复用性。我在抽象的过程中不仅仅需要关注现实与抽象之间的联系,还需要关注它们之间的内部属性的联系。在lab2中,我没有认真关注过围棋与国际象棋之间相似与不同的地方,而是单纯地将二者相同的操作放入了一个接口中,二者再同时implements接口并实现接口中的方法。在完成lab3之后,我对于复用性有了更好的理解。
package P3.Piece;
/**
* The general function of two pieces type.
*
* @author 12648
*
*/
public interface Piece {
/**
* Get piece's color.
*
* @return piece's color
*/
public String getPieceColor();
/**
* Get piece's x coordinate.
*
* @return piece's x coordinate
*/
public int getPieceX();
/**
* Get piece's y coordinate.
*
* @return
*/
public int getPieceY();
/**
* Get piece's state.
*
* @return piece's state
*/
public int getPieceState();
/**
* Change a piece's position.
*
* @param piece's target x coordinate, must be nonnegative
* @param piece's target y coordinate, must be nonnegative
* @return true if set piece successfully, or false if not
*/
public boolean setPiece(int x, int y);
/**
* Remove a piece from board.
*/
public void removePiece();
}
package P3.Piece;
import P3.Position.*;
public class GoPiece implements Piece{
// TODO fields
private String color;
private int state;
private Position position;
// Abstraction function:
// TODO
// 围棋棋子仅有颜色color和坐标position作为其参数
// state 是棋子状态, 为了方便判断棋子的状态,例如在棋盘上,未放置到棋盘上,或已经从棋盘上移除等
// state = -1, 表示该棋子已从棋盘上移除, 此时的坐标应为(-1, -1)
// state = 0, 表示该棋子还未放置到棋盘上
// state = 1, 棋子已经放置到了棋盘上
// Representation invariant:
// TODO
// 棋子颜色和位置不能为空,state必须为(-1, 0, 1)中的一个
// Safety from rep exposure:
// TODO
// 参数变量为private类型
// color为immutable类型
// TODO constructor
public GoPiece(String color, int state, int x, int y){
this.color = color;
this.state = state;
position = new Position(x, y);
}
// TODO checkRep
public boolean checkRep() {
if(color == null || position == null) {
return false;
}
if(state == -1 || state ==0 || state == 1) {
return true;
}
return false;
}
// TODO methods
@Override
public String getPieceColor(){
return color;
}
@Override
public int getPieceX() {
return position.getCoordinateX();
}
@Override
public int getPieceY() {
return position.getCoordinateY();
}
@Override
public int getPieceState() {
return state;
}
@Override
public boolean setPiece(int x, int y) {
position.changePosition(x, y);
state = 1;
assert(checkRep());
return true;
}
@Override
public void removePiece() {
position.changePosition(-1, -1);
state = -1;
assert(checkRep());
}
// TODO toString()
@Override
public String toString() {
return color + "; State:" + state + "; Position:{" + position.getCoordinateX() +", " + position.getCoordinateY() + "}";
}
}
package P3.Piece;
import P3.Position.*;
public class ChessPiece implements Piece{
// TODO fields
private String color;
private String label;
private int state;
private Position position;
// Abstraction function:
// TODO
// 国际象棋有颜色color, 名称label和坐标position作为其参数
// state 是棋子状态, 为了方便判断棋子的状态,例如在棋盘上,未放置到棋盘上,或已经从棋盘上移除等
// state = -1, 表示该棋子已从棋盘上移除
//state = 1, 棋子已经放置到了棋盘上
// Representation invariant:
// TODO
// 棋子颜色,标签和位置不能为空,state必须为(-1, 1)中的一个
// Safety from rep exposure:
// TODO
// 参数变量为private类型
// color和label为immutable类型
// TODO constructor
public ChessPiece(String color, String label, int state, int x, int y) {
this.color = color;
this.label = label;
this.state = state;
position = new Position(x, y);
assert(checkRep());
}
// TODO checkRep
public boolean checkRep() {
if(color.equals(null) || label.equals(null)) {
return false;
}
if(state == -1 || state == 1) {
return true;
}
return false;
}
// TODO methods
@Override
public String getPieceColor() {
return color;
}
/**
* Get label of piece.
*
* @return label of piece
*/
public String getPieceLabel() {
return label;
}
@Override
public int getPieceX() {
return position.getCoordinateX();
}
@Override
public int getPieceY() {
return position.getCoordinateY();
}
@Override
public int getPieceState() {
return state;
}
@Override
public boolean setPiece(int x, int y) {
if(position.getCoordinateX() == x && position.getCoordinateY() == y) {
return false;
}
position.changePosition(x, y);
assert(checkRep());
return true;
}
@Override
public void removePiece() {
position.changePosition(-1, -1);
state = -1;
assert(checkRep());
}
// TODO toString()
@Override
public String toString() {
return color + "; Label:" + label + "; State:" + state + "; Position:{" + position.getCoordinateX() +", " + position.getCoordinateY() + "}";
}
}
可以看到我在lab2中对于复用性基本没有任何的使用。虽然有想要通过复用降低工作量的想法,但明显没有很好地完成。
而在lab3中,一个非常重要的点就是复用性。简单地举个例子就是不同的Planning Entry具有的相同操作会放入顶层接口,并且通过委托和继承等多种方法尽可能实现复用降低工作量和成本。
package PlanningEntry;
import java.util.List;
import Location.Location;
import Timeslot.Timeslot;
public interface PlanningEntry<R> {
/**
* Create a Planning
*/
public void createPlanning(String label);
/**
* Set timeslot.
*
* @param start time
* @param end time
*/
public void setTimeslot(List<Timeslot> timeslot);
/**
* Allocate resource for the planning.
*
* @param resource
*/
public void allocatedPlanning(R resource);
/**
* Make a planning start to work.
*
*/
public void startPlanning();
/**
* Cancel a planning.
*
*/
public void cancelPlanning();
/**
* Complete a planning.
*
*/
public void completePlanning();
/**
* Get label of the planning.
*
* @return label of the planning
*/
public String getLabel();
/**
* Get state of the planning.
*
* @return state of the planning
*/
public String getState();
/**
* Get resource of the planning.
*
* @return resource
*/
public R getResource();
/**
* Get location with a correct index.
*
* @param index of this location
* @return location on this index
*/
public Location getLocation(int index);
/**
* Get locations' size.
*
* @return locations' size
*/
public int getLocationsSize();
/**
* Get timeslots' size.
*
* @return timeslots' size
*/
public int getTimeslotsSize();
/**
* Get start time of the planning.
*
* @return start time of the planning
*/
public String getStartTime(int index);
/**
* Get end time of the planning.
*
* @return end time of the planning
*/
public String getEndTime(int index);
}
可以看到这就是顶层容器,很多Planning Entry共用的操作都会放入这个接口之中。
package PlanningEntry;
import java.util.ArrayList;
import java.util.List;
import Location.Location;
import State.*;
import Timeslot.Timeslot;
public class CommonPlanningEntry<R> implements PlanningEntry<R> {
protected String label; //名称
protected R resource;
protected List<Location> locations = new ArrayList<Location>();
protected List<Timeslot> timeslots; //时间对
protected Context context = new Context(); //状态改变
@Override
public void createPlanning(String label) {
// TODO Auto-generated method stub
this.label = label;
this.context.setState(new Waiting());
}
@Override
public void setTimeslot(List<Timeslot> timeslots) {
// TODO Auto-generated method stub
this.timeslots = timeslots;
}
@Override
public void allocatedPlanning(R resource) {
// TODO Auto-generated method stub
this.resource = resource;
context.setState(new Allocated());
}
@Override
public void startPlanning() {
// TODO Auto-generated method stub
context.setState(new Running());
}
@Override
public void cancelPlanning() {
// TODO Auto-generated method stub
context.setState(new Canceled());
}
@Override
public void completePlanning() {
// TODO Auto-generated method stub
context.setState(new Ended());
}
@Override
public String getLabel() {
// TODO Auto-generated method stub
return label;
}
@Override
public String getState() {
// TODO Auto-generated method stub
return context.getState();
}
@Override
public R getResource() {
// TODO Auto-generated method stub
return resource;
}
@Override
public Location getLocation(int index) {
// TODO Auto-generated method stub
return locations.get(index);
}
@Override
public int getLocationsSize() {
// TODO Auto-generated method stub
return locations.size();
}
@Override
public int getTimeslotsSize() {
// TODO Auto-generated method stub
return timeslots.size();
}
@Override
public String getStartTime(int index) {
// TODO Auto-generated method stub
return timeslots.get(index).getStartTime();
}
@Override
public String getEndTime(int index) {
// TODO Auto-generated method stub
return timeslots.get(index).getEndTime();
}
}
这是implements了接口的次层父类,后面的具体计划项类只需要extends这个类就可以实现以上的功能。
我在lab2其实也可以使用同样的方法,就是使用一个implements了顶层接口的次层具体类,后面的具体类可以extends这个类,虽然在lab2中哪怕不使用这种方式也能完成且工作量增加不大,但是如果还需要增加中国象棋、井字棋等更多的棋类的时候,工作量就会大大增加。
其次就是各种各样的设计方法。在lab2中我没有使用到任何的设计方法,而在lab3中我使用了外观设计模式(Facade method)、状态模式(State method)、工厂模式(Factory method)、策略模式(Strategy method)等等设计方法,同时还学习了其他的实用的设计方法比如装饰者模式(Decorate method)、模板模式(Template method)、观察者模式(Observe method)等等一系列的设计方法。通过这些设计方法,我能够更好地设计出一套完整并且实用度高、复用性强、可维护性十分乐观的ADT。