前言: 本来准备边写博客边做作业的,然后发现网站的证书过期了,于是就去整了一下自动安装证书的脚本。 > 这个脚本本来一开始安装证书就有的,只不过那个时候是我手动安装的证书。 因为写反了两行代码……这就让我 debug 了一个晚上,最后弄到我把服务器直接重新恢复到了几天前。 最后发现 `fullchain.cer` 这个文件和之前的格式不太对,这才最后解决了这个问题。 > 我可真是个⑨。 下面是第二次作业。 ## 1. 阅读程序,回答问题 程序代码: ```java class Contained { public void disp() { System.out.println("disp() of Contained Object"); } } public class Container { private Contained c; //以下为结构 A Container(){ c = new Contained(); } //以下为结构 B public Contained getC(){ return c; } public void setC(Contained c) { this.c = c; } public static void main(String[] args) { Container container = new Container(); Contained contained = new Contained(); container.setC(contained); } } ``` 1. 请解释程序段的设计使用了程序设计的那些设计原则 最明显的就是**单一职责原则**,在 `Contained` 中就只有一个 `disp()` 方法。 另外,在 `Container` 中包含了一个 `private` 的 `Contained` 勉强可以算是体现了**迪米特法则**。 还有,在 `Container` 中包含了一个 `Contained` 也可以算是书上说的**合成/聚合复用原则**。 > 虽然六大设计原则里面好像没有那个合成/聚合复用原则。 2. 请画出本段程序的 UML 类图 ![UML-1][1] 3. 请将本程序修改为使用继承来实现复用 ```java class Contained { public void disp() { System.out.println("disp() of Contained Object"); } } public class Container extends Contained { private Contained c; Container(){ c = new Contained(); } public Contained getC(){ return c; } public void setC(Contained c) { this.c = c; } public static void main(String[] args) { Container container = new Container(); Contained contained = new Contained(); container.setC(contained); container.c.disp(); //测试 } } ``` 4. 请解释面向对象程序设计中继承(Inheritance)的优点与缺点 优点: - 新的实现很容易,因为大部分是继承而来的 - 很容易修改和扩展已有的实现 缺点: - 打破了封装,因为基类向子类暴露了实现细节 - 白盒重用,因为基类的内部细节通常对子类是可见的 - 当父类的实现改变时可能要相应的对子类做出改变 - 不能在运行时改变由父类继承来的实现 ## 2. 指出伪代码错误的设计错误及修改 伪代码如下: ```java public void Parse() { StringReader reader = new StringReader(scriptTextToProcess); StringBuilder scope = new StringBuilder(); string line = reader.ReadLine(); scope = new StringBuilder(); while (line != null) { switch (line[0]) { case '$': // Process the entire "line" as a variable, // i.e. add it to a collection of KeyValuePair. line.AddToVariables(); break; case '!': // Depending of what comes after the '!' character, // process the entire "scope" or command in "line". if (line == "!execute") scope.ExecuteScope(); else if (line.StartsWith("!custom_command")) line.RunCustomCommand(scope); else if (line == "!single_line_directive") line.ProcessDirective(); break; default: // No processing directive, i.e. add the "line" // to the current scope. scope.Append(line); break; } line = reader.ReadLine(); } } ``` 这一段伪代码功能实际上没有什么问题,但是在程序设计的方法原则上却犯了很多的错误。 比如: - 这里把所有功能都写在这个 `Parse()` 函数里面,这个就违反了我们的**单一职责原则**; - `switch` 语句套 `if else` 语句,不同的操作都放在了一块,这里明显的违反了**接口隔离原则**; - 还有在最后结束的时候,函数开的 `StringReader` 这个输入流没有关闭,不关闭会一直占用资源,显然这样是不应该的。 修改后的代码: ```java public void NewProcess(String line,StringBuilder scope) { if(line=="!execute") scope.ExecuteScope(); else if(line.StartsWith("!custom_command")) line.RunCustomCommand(scope); else if(line == "!single_line_directive") line.ProcessDirective(); } public void StringProcess(String line,StringBuilder scope) { if(line[0]=='$') line.AddToVariables(); else if(line[0]=='!') NewProcess(line,scope); else scope.Append(line); } public void Parse() { StringReader reader = new StringReader(scriptTextToProcess); StringBuilder scope = new StringBuilder(); string line = reader.ReadLine(); scope = new StringBuilder(); while(line != null) { StringProcess(line,scope); line = reader.ReadLine(); } reader.close(); } ``` ## 3. 用工厂模式实现一个加减乘除的控制台程序 > 这里先吐槽一下题面的输入两个数再用一元运算符 "$\sqrt {\ \ }$",这个怎么回事啊,给两个数都开根号?相乘后开根号? 直接上代码吧: ```java import java.util.*; import java.math.*; public interface Calculator() { void Calculate(double a,double b); } public class Plus implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println(a+"+"+b+"="+(a+b)+"\n"); } } public class Subtract implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println(a+"-"+b+"="+(a-b)+"\n"); } } public class Multi implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println(a+"*"+b+"="+(a*b)+"\n"); } } public class Divide implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println(a+"+"+b+"="+(a/b)+"\n"); } } public class Sqrt implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println("sqrt("+a+")="+Math.sqrt(a)+" sqrt("+b+")="+Math.sqrt(b)+"\n"); } } public class CalculatorFactory{ doiuble a,b; char type; public void ConsoleRead() { system("cls"); system.out.println("Chioce a calculate type:\na:x+y\nb:x-y\nc:x*y\nd:x/y\ne:sqrt(a) sqrt(b)\nFor example:x y a\n"); Scanner scan = new Scanner(System.in); a = scan.nextDouble(); b = scan.nextDouble(); type = scan.next(); scan.close(); } public void Process(double a,double b,char type){ if(type=='a'){ Plus.Calculate(a,b); } else if(type=='b'){ Subtract.Calculate(a,b); } else if(type=='c'){ Multi.Calculate(a,b); } else if(type=='d'){ Divide.Calculate(a,b); } else if(type=='e'){ Sqrt.Calculate(a,b); } else throw IOException; } } public class FactoryTest{ public static void main(String args[]) { while(true) { try{ CalculatorFactory test = new CalculatorFactory(); test.ConsoleRead(); test.Process(); } catch(java.lang.ArithmeticException e){ System.err.println("Math error!"); } catch(IOException e){ System.err.println("Invailed Input!"); } } } } ``` UML 图: ![UML-2][2] ## 4. 解释 iterator 学了工厂模式之后,仔细这么一想,再结合一下自己对 C++ STL 的认识,`iterator(迭代器)`确实是最简单最明显的一个工厂模式的实例。 > 虽然题目要求解释的是 Java 中 collection 的 iterator,不过这里也正好把 C++ 的 iterator 也写了吧! 在 C++ 的 STL 中,不论是 `set`、`vector`、`map` 等等,都有一个 `iterator`,也许是 `set::iterator` 或者是其他类型的,它们其实都是通过了一个公共的 `iterator` 接口。 这里上一个 C++ 的实例代码: ```cpp #include //万能头文件 set st; vector number; int main() { int num[]={1,2,34,45,2,324,45,6,3}; for(int i = 0;i < 9;i++){ number.push_back(num[i]); } for(vector::iterator it=number.begin();it!=number.end();it++){ st.insert(*it); } for(setiterator it=st.begin();it!=st.end();it++){ printf("%d ",*it); } return 0; } ``` Java 中的 Collection 的 iterator 其实也是类似的,在这里我们是 `set`,但是我们还可以是 `string`,还可以是结构体~~当然,set 的结构体你得重载写个 "<" 的判断~~。也就是说,无论我们的 `set` 是什么类型的,都有对应的 `iterator`,或者说是在需要的时候进行 `iterator` 接口的实例化。而这样,无论我们创建什么样子的 `set`,都可以使用后面实例化的 `iterator`,这样也就是 `iterator` 工厂模式的说明,或者说是体现。 [1]: https://hodam.top/myfile/image/h2_1.png [2]: https://hodam.top/myfile/image/t3.png Loading... 前言: 本来准备边写博客边做作业的,然后发现网站的证书过期了,于是就去整了一下自动安装证书的脚本。 > 这个脚本本来一开始安装证书就有的,只不过那个时候是我手动安装的证书。 因为写反了两行代码……这就让我 debug 了一个晚上,最后弄到我把服务器直接重新恢复到了几天前。 最后发现 `fullchain.cer` 这个文件和之前的格式不太对,这才最后解决了这个问题。 > 我可真是个⑨。 下面是第二次作业。 ## 1. 阅读程序,回答问题 程序代码: ```java class Contained { public void disp() { System.out.println("disp() of Contained Object"); } } public class Container { private Contained c; //以下为结构 A Container(){ c = new Contained(); } //以下为结构 B public Contained getC(){ return c; } public void setC(Contained c) { this.c = c; } public static void main(String[] args) { Container container = new Container(); Contained contained = new Contained(); container.setC(contained); } } ``` 1. 请解释程序段的设计使用了程序设计的那些设计原则 最明显的就是**单一职责原则**,在 `Contained` 中就只有一个 `disp()` 方法。 另外,在 `Container` 中包含了一个 `private` 的 `Contained` 勉强可以算是体现了**迪米特法则**。 还有,在 `Container` 中包含了一个 `Contained` 也可以算是书上说的**合成/聚合复用原则**。 > 虽然六大设计原则里面好像没有那个合成/聚合复用原则。 2. 请画出本段程序的 UML 类图 ![UML-1][1] 3. 请将本程序修改为使用继承来实现复用 ```java class Contained { public void disp() { System.out.println("disp() of Contained Object"); } } public class Container extends Contained { private Contained c; Container(){ c = new Contained(); } public Contained getC(){ return c; } public void setC(Contained c) { this.c = c; } public static void main(String[] args) { Container container = new Container(); Contained contained = new Contained(); container.setC(contained); container.c.disp(); //测试 } } ``` 4. 请解释面向对象程序设计中继承(Inheritance)的优点与缺点 优点: - 新的实现很容易,因为大部分是继承而来的 - 很容易修改和扩展已有的实现 缺点: - 打破了封装,因为基类向子类暴露了实现细节 - 白盒重用,因为基类的内部细节通常对子类是可见的 - 当父类的实现改变时可能要相应的对子类做出改变 - 不能在运行时改变由父类继承来的实现 ## 2. 指出伪代码错误的设计错误及修改 伪代码如下: ```java public void Parse() { StringReader reader = new StringReader(scriptTextToProcess); StringBuilder scope = new StringBuilder(); string line = reader.ReadLine(); scope = new StringBuilder(); while (line != null) { switch (line[0]) { case '$': // Process the entire "line" as a variable, // i.e. add it to a collection of KeyValuePair. line.AddToVariables(); break; case '!': // Depending of what comes after the '!' character, // process the entire "scope" or command in "line". if (line == "!execute") scope.ExecuteScope(); else if (line.StartsWith("!custom_command")) line.RunCustomCommand(scope); else if (line == "!single_line_directive") line.ProcessDirective(); break; default: // No processing directive, i.e. add the "line" // to the current scope. scope.Append(line); break; } line = reader.ReadLine(); } } ``` 这一段伪代码功能实际上没有什么问题,但是在程序设计的方法原则上却犯了很多的错误。 比如: - 这里把所有功能都写在这个 `Parse()` 函数里面,这个就违反了我们的**单一职责原则**; - `switch` 语句套 `if else` 语句,不同的操作都放在了一块,这里明显的违反了**接口隔离原则**; - 还有在最后结束的时候,函数开的 `StringReader` 这个输入流没有关闭,不关闭会一直占用资源,显然这样是不应该的。 修改后的代码: ```java public void NewProcess(String line,StringBuilder scope) { if(line=="!execute") scope.ExecuteScope(); else if(line.StartsWith("!custom_command")) line.RunCustomCommand(scope); else if(line == "!single_line_directive") line.ProcessDirective(); } public void StringProcess(String line,StringBuilder scope) { if(line[0]=='$') line.AddToVariables(); else if(line[0]=='!') NewProcess(line,scope); else scope.Append(line); } public void Parse() { StringReader reader = new StringReader(scriptTextToProcess); StringBuilder scope = new StringBuilder(); string line = reader.ReadLine(); scope = new StringBuilder(); while(line != null) { StringProcess(line,scope); line = reader.ReadLine(); } reader.close(); } ``` ## 3. 用工厂模式实现一个加减乘除的控制台程序 > 这里先吐槽一下题面的输入两个数再用一元运算符 "$\sqrt {\ \ }$",这个怎么回事啊,给两个数都开根号?相乘后开根号? 直接上代码吧: ```java import java.util.*; import java.math.*; public interface Calculator() { void Calculate(double a,double b); } public class Plus implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println(a+"+"+b+"="+(a+b)+"\n"); } } public class Subtract implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println(a+"-"+b+"="+(a-b)+"\n"); } } public class Multi implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println(a+"*"+b+"="+(a*b)+"\n"); } } public class Divide implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println(a+"+"+b+"="+(a/b)+"\n"); } } public class Sqrt implements Calculator{ @Override public void Calculate(double a,double b) { System.out.println("sqrt("+a+")="+Math.sqrt(a)+" sqrt("+b+")="+Math.sqrt(b)+"\n"); } } public class CalculatorFactory{ doiuble a,b; char type; public void ConsoleRead() { system("cls"); system.out.println("Chioce a calculate type:\na:x+y\nb:x-y\nc:x*y\nd:x/y\ne:sqrt(a) sqrt(b)\nFor example:x y a\n"); Scanner scan = new Scanner(System.in); a = scan.nextDouble(); b = scan.nextDouble(); type = scan.next(); scan.close(); } public void Process(double a,double b,char type){ if(type=='a'){ Plus.Calculate(a,b); } else if(type=='b'){ Subtract.Calculate(a,b); } else if(type=='c'){ Multi.Calculate(a,b); } else if(type=='d'){ Divide.Calculate(a,b); } else if(type=='e'){ Sqrt.Calculate(a,b); } else throw IOException; } } public class FactoryTest{ public static void main(String args[]) { while(true) { try{ CalculatorFactory test = new CalculatorFactory(); test.ConsoleRead(); test.Process(); } catch(java.lang.ArithmeticException e){ System.err.println("Math error!"); } catch(IOException e){ System.err.println("Invailed Input!"); } } } } ``` UML 图: ![UML-2][2] ## 4. 解释 iterator 学了工厂模式之后,仔细这么一想,再结合一下自己对 C++ STL 的认识,`iterator(迭代器)`确实是最简单最明显的一个工厂模式的实例。 > 虽然题目要求解释的是 Java 中 collection 的 iterator,不过这里也正好把 C++ 的 iterator 也写了吧! 在 C++ 的 STL 中,不论是 `set`、`vector`、`map` 等等,都有一个 `iterator`,也许是 `set<int>::iterator` 或者是其他类型的,它们其实都是通过了一个公共的 `iterator` 接口。 这里上一个 C++ 的实例代码: ```cpp #include <bits/stdc++.h> //万能头文件 set<int> st; vector<int> number; int main() { int num[]={1,2,34,45,2,324,45,6,3}; for(int i = 0;i < 9;i++){ number.push_back(num[i]); } for(vector<int>::iterator it=number.begin();it!=number.end();it++){ st.insert(*it); } for(set<int>iterator it=st.begin();it!=st.end();it++){ printf("%d ",*it); } return 0; } ``` Java 中的 Collection 的 iterator 其实也是类似的,在这里我们是 `set<int>`,但是我们还可以是 `string`,还可以是结构体~~当然,set 的结构体你得重载写个 "<" 的判断~~。也就是说,无论我们的 `set` 是什么类型的,都有对应的 `iterator`,或者说是在需要的时候进行 `iterator` 接口的实例化。而这样,无论我们创建什么样子的 `set`,都可以使用后面实例化的 `iterator`,这样也就是 `iterator` 工厂模式的说明,或者说是体现。 [1]: https://hodam.top/myfile/image/h2_1.png [2]: https://hodam.top/myfile/image/t3.png Last modification:April 9, 2019 © Allow specification reprint Support Appreciate the author Like 如果觉得我的文章对你有用,请随意赞赏