Home

SC实验三

字数统计: 4.6k阅读时长: 18 min
2019/04/16 Share

 该实验面向可复用性进行设计,需要学习掌握一定的设计模式针对不同的应用进行设计。而我本人选择的三个应用:StellarSystem(太阳系恒星模型)、AtomStructure(原子结构模型)、SocialNetworkCircle(社交网络)。


可复用啊!!! L,E。

不同对象的面向复用的设计(Reusability)

CircularOrbit<L,E>

Mutable的类型。
 设计新的接口CircularOrbit<L,E>,其中L和E分别代表多轨道系统的中心点物体和轨道物体。可有的操作:

  • 创建一个空的 CircularOrbit 对象
  • 增加一条轨道、去除一条轨道
  • 增加中心点物体
  • 向特定轨道上增加一个物体(不考虑物理位置)
  • 增加中心点物体和一个轨道物体之间的关系
  • 增加两个轨道物体之间的关系
  • 从外部文件读取数据构造轨道系统对象

Track

Immutable的ADT。
 需要设计rep:1.半径

L(CircularOrbit<L,E>中的L)

 所设计的表征中心点物体的类。

  • StellarSystem:中心点物体为恒星
  • AtomStructure:中心点物体为原子核
  • SocialNetworkCircle:中心点物体为人

PhysicalObject

Immutable的ADT。
 实现 ConcreteCircularOrbit<L,E>中的 E,即代表分布在不同轨道上的物 体类 PhysicalObject。可以是接口,也可以是抽象类。 考虑到不同应用中包含不同类型的轨道物体,需从 PhysicalObject 派生子 类型,通过 override 实现 PhysicalObject 中的各个接口方法或抽象方法,也可 根据应用需求来增加子类的特有属性和方法。

  • StellarSystem:轨道物体为行星
  • AtomStructure:轨道物体为电子
  • SocialNetworkCircle:轨道物体为人

可复用的API设计

 API-Application Programming Interface. 何为API→点此了解

  • 计算多轨道系统中各轨道上物体分布的熵值。
    double getObjectDistributionEntropy(CircularOrbit c)
  • 计算任意两个物体之间的最短逻辑距离。
    int getLogicalDistance (CircularOrbit c, E e1, E e2)若两物体无联系,距离无穷大。(Lab1中P3已实现该功能)
  • 计算任意两个物体之间的物理距离。(若有具体位置,可在直角坐标系中计算)
    double getPhysicalDistance (CircularOrbit c, E e1, E e2)
  • //TODO 计算两个多轨道系统之间的差异

第三方API复用

 实现可视化

设计模式应用

(1) 构造 Track、PhysicalObject 等对象时,请使用 factory method 设计模式。
(2) 构造 ConcreteCircularOrbit 对象时,针对不同应用中所需的不同类型的 L 和 E,使用 abstract factorybuilder 设计模式。
(3) Iterator设计模式,在遍历各PhysicalObject对象时使用。
(4) 可复用API时遵循façade 设计模式,将所有 API 放置在 helper类CircularOrbitAPIs当中
(5) StellarSystem 应用中使用decorator设计模式
(6) AtomStructure 应用中,请使用 statememento 设计模式管理电子跃迁 的状态,并可进行状态的恢复。意即:可保存电子每次跃迁前后的轨道信息。


实验涉及知识

继承和委托

 继承inheritance.可能涉及到override,较易理解。下面重点分析delegation(委托)
简而言之,delegation是一个对象需要依赖其他的对象所实现功能的行为。
 分为Explicit和Implicit两种。
 -e.g. theSorter is delegating functionality to some Comparator

LSP(Liskov替换原则)

  • Subtypes can add, but not remove methods
     子类型可以增加方法,但不可删。
  • Concrete class must implement all undefined methods
     子类型需要实现抽象类型中的所有未实现方法。
  • Overriding method must return same type or subtype
     子类型中重写的方法必须有相同或子类型的返回值。
  • Overriding method must accept the same parameter types
     子类型中重写的方法必须使用同样类型的参数。
  • Overriding method may not throw additional exceptions
     子类型中重写的方法不能抛出额外的异常。
    而且满足下列条件
  • Same or stronger invariants 更强的不变量
  • Same or weaker preconditions 更弱的前置条件
  • Same or stronger postconditions 更强的后置条件

liskov

抽象类?

 抽象类与多态有关,了解抽象类->为什么使用抽象类?

OO Design Principles:SOLID

  • (SRP) The Single Responsibility Principle 单一责任原则
  • (OCP) The Open-Closed Principle 开放-封闭原则
  • (LSP) The Liskov Substitution Principle Liskov替换原则
  • (DIP) The Dependency Inversion Principle 依赖转置原则
  • (ISP) The Interface Segregation Principle 接口聚合原则

SRP

一个类,一个责任。(下图为反例)
srp

OCP

对扩展性的开放,对修改的封闭。

  • 模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化
  • 但模块自身的代码是不应被修改的
  • 扩展模块行为的一般途径是修改模块的内部实现
  • 如果一个模块不能被修改,那么它通常被认为是具有固定的行为

 关键技术:抽象

LSP

前文

ISP 接口隔离

不能强迫客户端依赖于它们 不需要的接口:只提供必需的接口
isp

DIP 依赖转置

  • 抽象的模块不应依赖于具体的模块
  • 具体应依赖于抽象

dip
 关键词:隔离


Design Patterns

一览Design Patterns的框架
design patterns

Decorator

 为什么要这种模式:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
Problem:如何为对象增加不同侧面的特性。
Solution:对每一个特性构造子类,通过委派机制增加到对象上。
decorator
 - 抽象构件类(Component):给出一个抽象的接口,用以规范准备接收附加责任的对象
 - 具体构件类(ConcreteComponent):定义一个具体的准备接受附加责任的类,其必须实现Component接口。
 - 装饰者类(Decorator):有一个构件(Conponent)对象的成员变量,并定义一个和抽象构件一致的接口(我们就是通过这个一致的接口中的东西来加东西)。
 - 具体装饰者类(Concrete Decoratator):定义给构件对象“贴上”附加责任。
 每次想给具体构建类的某个实例加上一点东西的时候,我们做出要加上的东西对应的具体装饰者类,以具体构建类作为构造器的输入参数,然后调用装饰类的方法输出装饰后的结果。注意上述所有的变量的声明都是他们的公共接口。
decorator模式.其中关键的decorator为material里面添加的内容。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Beef extends Material{

public Beef(Noodle noodle){
super(noodle);
}

@Override
public String getDescriptin() {
return noodle.getDescriptin()+" + 牛肉";
}

@Override
public double cost() {
return noodle.cost()+2.0;
}

}

facade(外观模式)

 外观模式是为了解决类与类之间的依赖关系的,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口。
 应用于小的接口,目的是提供一个进行相似工作的接口。详细理解外观模式
例如:
facade

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SystemA { 
public void operationA(){
System.out.println("operation a...");
}
}
public class SystemB {
public void operationB() {
System.out.println("operation b...");
}
}
public class SystemC {
public void operationC() {
System.out.println("operation c...");
}
}

facade

1
2
3
4
5
6
7
8
9
10
public class Facade { 
public void wrapOperation() {
SystemA a = new SystemA();
a.operationA();
SystemB b = new SystemB();
b.operationB();
SystemC c = new SystemC();
c.operationC();
}
}

客户端

1
2
3
4
5
6
public class Client { 
public static void main(String[] args) {
Facade facade = new Facade();
facade.wrapOperation();
}
}

strategy模式

  • 问题描述:对于一个特定问题存在不同的算法,客户能够在动态运行期间(at run time)在这些算法之间切换。
  • e.g. Sorting a list of customers(bubble sort, mergesort, quicksort)
  • 解决: 创造一个接口,对算法中每个variant创建一个实现类。(implementing class)

strategy

state模式

 用一句话来表述,状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
状态模式所涉及到的角色:

  • 环境(Context)角色,也成上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
  • 抽象状态(State)角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为。
  • 具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。

state
详细阅读→状态模式

Iterator

  • 问题所在:客户需要访问容器内的所有元素,无论该容器的类型如何
  • 解决方案:A strategy pattern for iteration
  • 应用场景:(1)访问一个聚合对象而不需暴露对象内部表示;(2)支持对聚合对象的多种遍历;(3)对遍历不同的对象,提供统一的接口。
    iterator
    具体示例iterator迭代器设计模式迭代器模式

Factory Method pattern

Also known as “Virtual Constructor”. 工厂方法案例1工厂方法案例2
 应用场景:当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体 创建的实例时,用工厂方法。 定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。
Factory method

  • 抽象产品角色(Product):定义产品的接口
  • 具体产品角色(ConcreteProduct) :实现接口Product的具体产品类
  • 抽象工厂角色(Creator) :声明工厂方法(FactoryMethod),返回一个产品
  • 真实的工厂(ConcreteCreator):实现FactoryMethod工厂方法,由客户调用,返回一个产品的实例

 开工厂方法就是不想把子类名字暴露,把创建和返回隔离开。在子类实现过程当中不会暴露。

1
2
3
public Track getTrack(int inputRadius) {
return new SocialTrack(inputRadius);
}

Abstract Factory

  创建的不是一个完整产品,而是“产品族”(遵循 固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的 object,各产品创建过程对client可见,但“搭配”不能改变。
  本质上,Abstract Factory是把多类产品的factory method组合在一起
fm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
abstract class AbstractProductA {  // A产品
public abstract void productMethod();
};

abstract class AbstractProductB { // B产品
public abstract void productMethod();
};

abstract class AbstracFactory { // 产品族的抽象工厂
public abstract AbstractProductA createProductA();
public abstract AbstractProductB createProductB();
};


class ProductA1 extends AbstractProductA {
@Override
public void productMethod() {
System.out.println("ProductA1");
}
};

class ProductB1 extends AbstractProductB {
@Override
public void productMethod() {
System.out.println("Product B1");
}
};

class ProductA2 extends AbstractProductA {
@Override
public void productMethod() {
System.out.println("Product A2");
}
};

class ProductB2 extends AbstractProductB {
@Override
public void productMethod() {
System.out.println("Product B2");
}
};

class Factory1 extends AbstracFactory {
@Override
public AbstractProductA createProductA() {
System.out.println("Factory1");
return new ProductA1();
}

@Override
public AbstractProductB createProductB() {
System.out.println("Factory1");
return new ProductB1();
}
};

class Factory2 extends AbstracFactory {
@Override
public AbstractProductA createProductA() {
System.out.println("Factory2");
return new ProductA2();
}

@Override
public AbstractProductB createProductB() {
System.out.println("Factory2");
return new ProductB2();
}
};

public class Client {
public static void main(String arg[]){
AbstracFactory FAC;
AbstractProductA PA;
AbstractProductB PB;

FAC = new Factory1();
PA = FAC.createProductA();
PA.productMethod();
PB = FAC.createProductB();
PB.productMethod();

System.out.println("----------------");

FAC = new Factory2();
PA = FAC.createProductA();
PA.productMethod();
PB = FAC.createProductB();
PB.productMethod();
}
};

builder

builder

  • Builder: 为创建一个Product对象的各个部件指定抽象接口。
  • ConcreteBuilder: 实现Builder的接口以构造和装配该产品的各个部件;定义并明确它所创建的表示;提供一个获取产品的接口。
  • Director: 构造一个使用Builder接口的对象。
  • Product: 表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程 。包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

 举例:去必胜客点pizza。服务员(director)推荐给你套餐(builder),套餐一:甜甜圈,辣酱和海鲜披萨(concretebuilder),套餐二:薯条,番茄酱和水果披萨(concretebuilder)……而具体的套餐内产品(product)要进行制作(构造):

1
2
3
private String sauce;
private String siders;
private String pizza;

配上一系列set操作,叮,做好了!

memento(备忘录模式)

设计模式之美:备忘录模式《java与模式》备忘录模式
 意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
 结构:
memento
橙子说

Originator发起人角色:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据
Memento备忘录角色:负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
Caretaker备忘录管理员角色:对备忘录进行管理、保存和提供备忘录。

实践出真知

Track

 构造当中用极复杂的方式实现本来很简单的数据结构。但是便于扩展应用。(后来想想我就是傻,一个Track有啥扩展的?)


test测试小插曲

assertEquals对double类型测试出错?
e1
 查阅资料-看异常中的提醒Use assertEquals(expected, actual, delta) to compare floating-point numbers, 即按照“预期值,真实值,误差值”的格式,只要在这个范围内就算正确。
 后来改成这个assertEquals( 2.2, stellarTrack.getRadius(),0.0);


科学记数法处理

 在实验中涉及到对number的处理

大于 10000 的数字按科学记数法表示(例如 1.9885e30 表示1.9885 ∗ 1030,但 e 之前数字的整数部分必 须在 1 到 9 的范围内,e 之后的数字只能是大于 3 的正整 数),小于 10000 的数字直接给出(例如 5912,103.193), 不能用科学计数法。小数点位数不限制。

以本人设计的Stellar(恒星系的中心物体)为例,域如下

1
2
3
private final String name;
private final double radius;
private final double weight;

如何根据要求转化为double类型的呢?橙子又说,BigDecimal!
this.radius = new BigDecimal(inputRadius).doubleValue(); //转化为科学记数法


为什么轨道上放不了物体?

发现如果直接用placeObject(Track inputtrack, E object)就直接能加上物体,而如果先加入轨道的话就不能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  public boolean placeObject(Track t, E object) {
if(tracks.contains(t) && objectsOnTrack.containsKey(t)) {
if(objectsOnTrack.get(t).contains(object)) {
System.out.println("The object has been on that Track"); //如果物体已在轨道,返回false
return false;
}
objectsOnTrack.get(t).add(object); //轨道在集合中,但物体不在
return true; //加入物体的信息,返回成功加入
}
else if(!tracks.contains(t)) { //轨道不含有t
tracks.add(t);
List<E> objects = new ArrayList<E>(); //创建轨道上一组物体
objects.add(object);
objectsOnTrack.put(t, objects);
}
// else if(objectsOnTrack.containsKey(t)) {
// objectsOnTrack.get(t).add(object);
// }
return true;
}

关键在于我的addTrack:

1
2
3
4
5
6
7
8
9
10
11
12
public boolean addTrack(Track inputTrack) {
if(tracks.contains(inputTrack)) {
System.out.println("The track has been there.");
return false;
}
tracks.add(inputTrack);

//轨道与物体对应关系上,建立新集合,放入匹配。
List<E> objects = new ArrayList<E>();
objectsOnTrack.put(inputTrack, objects);
return true;
}

 第一开始并没有在objectsOnTrack.put(inputTrack, objects);List<E> objects = new ArrayList<E>();,如果不事先放入一个集合进objectsOnTrack这个map的话,会返回空指针。


子类成员变量和父类

在我的SocialNetworkCircle和其父类ConcreteCircularOrbit中都有
private final List<Track> tracks; //人际关系,其中radius表示好友所在的轨道级数
 然而我在子类方法中调用父类的placeObject,其中会加入新轨道。父类placeObject代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public boolean placeObject(Track t, E object) {
if(tracks.contains(t) && objectsOnTrack.containsKey(t)) {
if(objectsOnTrack.get(t).contains(object)) {
System.out.println("The object has been on that Track"); //如果物体已在轨道,返回false
return false;
}
objectsOnTrack.get(t).add(object); //轨道在集合中,但物体不在
return true; //加入物体的信息,返回成功加入
}
else if(!tracks.contains(t)) { //轨道不含有t
tracks.add(t);

System.out.println(tracks.size());
List<E> objects = new ArrayList<E>(); //创建轨道上一组物体
objects.add(object);
objectsOnTrack.put(t, objects);
}

给的样例中,和中心人物有关的有四对关系,但其中只有两级轨道。一级好友和两级好友。
子类中

1
2
3
4
Track t = new SocialTrackFactory().getTrack(String.valueOf(distance));
tracks.add(t); //不然的话无法成功加入
placeObject(t, p1);
return true; //关系可能为负数,无限大,此时不加入。

 第3行为关键,如果注释掉的话每次都会new一个新的track,但是如果加上,只会new两个。猜测这个加入到父类的tracks中而非子类的。
 证据:
tracks

后来发现objectsOnTrack这个map也有问题,同样的操作会直接对父类的这个成员变量产生影响,唉,索性让我重写父类的方法再ctrl +c/+v。

续:后来得知我的子类的重复定义了父类的成员变量,然后调用一系列方法的时候会被父类成员变量覆盖。

保留两位小数

1
2
BigDecimal rounded = new BigDecimal(entropy);
double entropyRounded = rounded.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();

其中entrop是计算熵的时候用的。

何方异常是也?ConcurrentModificationException?

在写星体转动的时候,在position中进行了这样的操作。

1
2
3
4
5
6

for(Planet p : position.keySet()) {
······
position.remove(p);
position.put(p, sitha);
}

就会报错。

表示暴露实例分析

 纪念第一次通宵写程序。在获取轨道变化前的map时我的画风

1
2
3
for(Track track : objectsOnTrack.keySet()) {
memento.put(track,objectsOnTrack.get(track));
}

 想着直接把List塞进去了,但是后续清空objectsOnTrack的时候“似乎new”出来的这个memento(map类型)也会如影随形般变化,所以一定暴漏了。

1
2
3
4
5
6
7
for(Track track : objectsOnTrack.keySet()) {
List<Electron> e1 = new ArrayList<Electron>();
for(Electron e : objectsOnTrack.get(track)) {
e1.add(e);
}
memento.put(track,e1);
}

 如此安好!


 实验三算过去了,总结的也算一般般吧。一点点进步吧,加油!

CATALOG
  1. 1. 可复用啊!!! L,E。
  • 不同对象的面向复用的设计(Reusability)
    1. 1. CircularOrbit<L,E>
    2. 2. Track
    3. 3. L(CircularOrbit<L,E>中的L)
    4. 4. PhysicalObject
    5. 5. 可复用的API设计
    6. 6. 第三方API复用
    7. 7. 设计模式应用
  • 实验涉及知识
    1. 1. 继承和委托
    2. 2. LSP(Liskov替换原则)
    3. 3. 抽象类?
    4. 4. OO Design Principles:SOLID
      1. 4.1. SRP
      2. 4.2. OCP
      3. 4.3. LSP
      4. 4.4. ISP 接口隔离
      5. 4.5. DIP 依赖转置
  • Design Patterns
    1. 1. Decorator
    2. 2. facade(外观模式)
    3. 3. strategy模式
    4. 4. state模式
    5. 5. Iterator
    6. 6. Factory Method pattern
    7. 7. Abstract Factory
    8. 8. builder
    9. 9. memento(备忘录模式)
  • 实践出真知
    1. 1. Track
    2. 2. test测试小插曲
    3. 3. 科学记数法处理
    4. 4. 为什么轨道上放不了物体?
    5. 5. 子类成员变量和父类
    6. 6. 保留两位小数
    7. 7. 何方异常是也?ConcurrentModificationException?
    8. 8. 表示暴露实例分析