标题 & 代码高亮

This commit is contained in:
gdut-yy
2019-12-28 22:27:33 +08:00
parent 67f3f67fac
commit 893fb65d8c
17 changed files with 684 additions and 682 deletions

View File

@@ -1,4 +1,4 @@
Classes
# 第 10 章 Classes
with Jeff Langr
Image
@@ -24,7 +24,7 @@ Listing 10-1 outlines a class, SuperDashboard, that exposes about 70 public meth
Listing 10-1 Too Many Responsibilities
```java
public class SuperDashboard extends JFrame implements MetaDataUser
public String getCustomizerLanguagePath()
public void setSystemConfigPath(String systemConfigPath)
@@ -98,12 +98,12 @@ Listing 10-1 Too Many Responsibilities
public void showHelper(MetaObject metaObject, String propertyName)
// … many non-public methods follow …
}
```
But what if SuperDashboard contained only the methods shown in Listing 10-2?
Listing 10-2 Small Enough?
```java
public class SuperDashboard extends JFrame implements MetaDataUser
public Component getLastFocusedComponent()
public void setLastFocused(Component lastFocused)
@@ -111,7 +111,7 @@ Listing 10-2 Small Enough?
public int getMinorVersionNumber()
public int getBuildNumber()
}
```
Five methods isnt too much, is it? In this case it is because despite its small number of methods, SuperDashboard has too many responsibilities.
The name of a class should describe what responsibilities it fulfills. In fact, naming is probably the first way of helping determine class size. If we cannot derive a concise name for a class, then its likely too large. The more ambiguous the class name, the more likely it has too many responsibilities. For example, class names including weasel words like Processor or Manager or Super often hint at unfortunate aggregation of responsibilities.
@@ -129,13 +129,13 @@ Trying to identify responsibilities (reasons to change) often helps us recognize
Listing 10-3 A single-responsibility class
```java
public class Version {
public int getMajorVersionNumber()
public int getMinorVersionNumber()
public int getBuildNumber()
}
```
SRP is one of the more important concept in OO design. Its also one of the simpler concepts to understand and adhere to. Yet oddly, SRP is often the most abused class design principle. We regularly encounter classes that do far too many things. Why?
Getting software to work and making software clean are two very different activities. Most of us have limited room in our heads, so we focus on getting our code to work more than organization and cleanliness. This is wholly appropriate. Maintaining a separation of concerns is just as important in our programming activities as it is in our programs.
@@ -159,7 +159,7 @@ Consider the implementation of a Stack in Listing 10-4. This is a very cohesive
Listing 10-4 Stack.java A cohesive class.
```java
public class Stack {
private int topOfStack = 0;
List<Integer> elements = new LinkedList<Integer>();
@@ -181,7 +181,7 @@ Listing 10-4 Stack.java A cohesive class.
return element;
}
}
```
The strategy of keeping functions small and keeping parameter lists short can sometimes lead to a proliferation of instance variables that are used by a subset of methods. When this happens, it almost always means that there is at least one other class trying to get out of the larger class. You should try to separate the variables and methods into two or more classes such that the new classes are more cohesive.
Maintaining Cohesion Results in Many Small Classes
@@ -199,7 +199,7 @@ As a demonstration of what I mean, lets use a time-honored example taken from
Listing 10-5 PrintPrimes.java
```java
package literatePrimes;
public class PrintPrimes {
@@ -270,14 +270,14 @@ Listing 10-5 PrintPrimes.java
}
}
}
```
This program, written as a single function, is a mess. It has a deeply indented structure, a plethora of odd variables, and a tightly coupled structure. At the very least, the one big function should be split up into a few smaller functions.
Listing 10-6 through Listing 10-8 show the result of splitting the code in Listing 10-5 into smaller classes and functions, and choosing meaningful names for those classes, functions, and variables.
Listing 10-6 PrimePrinter.java (refactored)
```java
package literatePrimes;
public class PrimePrinter {
@@ -296,10 +296,10 @@ Listing 10-6 PrimePrinter.java (refactored)
}
}
```
Listing 10-7 RowColumnPagePrinter.java
```java
package literatePrimes;
import java.io.PrintStream;
@@ -369,10 +369,10 @@ Listing 10-7 RowColumnPagePrinter.java
this.printStream = printStream;
}
}
```
Listing 10-8 PrimeGenerator.java
```java
package literatePrimes;
import java.util.ArrayList;
@@ -443,7 +443,7 @@ Listing 10-8 PrimeGenerator.java
return multiple;
}
}
```
The first thing you might notice is that the program got a lot longer. It went from a little over one page to nearly three pages in length. There are several reasons for this growth. First, the refactored program uses longer, more descriptive variable names. Second, the refactored program uses function and class declarations as a way to add commentary to the code. Third, we used whitespace and formatting techniques to keep the program readable.
Notice how the program has been split into three main responsibilities. The main program is contained in the PrimePrinter class all by itself. Its responsibility is to handle the execution environment. It will change if the method of invocation changes. For example, if this program were converted to a SOAP service, this is the class that would be affected.
@@ -463,7 +463,7 @@ The Sql class in Listing 10-9 is used to generate properly formed SQL strings gi
Listing 10-9 A class that must be opened for change
```java
public class Sql { public Sql(String table, Column[] columns)
public String create()
public String insert(Object[] fields)
@@ -477,7 +477,7 @@ Listing 10-9 A class that must be opened for change
private String selectWithCriteria(String criteria)
private String placeholderList(Column[] columns)
}
```
The Sql class must change when we add a new type of statement. It also must change when we alter the details of a single statement type—for example, if we need to modify the select functionality to support subselects. These two reasons to change mean that the Sql class violates the SRP.
We can spot this SRP violation from a simple organizational standpoint. The method outline of Sql shows that there are private methods, such as selectWithCriteria, that appear to relate only to select statements.
@@ -488,7 +488,7 @@ What if we considered a solution like that in Listing 10-10? Each public interfa
Listing 10-10 A set of closed classes
```java
abstract public class Sql {
public Sql(String table, Column[] columns)
abstract public String generate();
@@ -543,7 +543,7 @@ Listing 10-10 A set of closed classes
public ColumnList(Column[] columns)
public String generate()
}
```
The code in each class becomes excruciatingly simple. Our required comprehension time to understand any class decreases to almost nothing. The risk that one function could break another becomes vanishingly small. From a test standpoint, it becomes an easier task to prove all bits of logic in this solution, as the classes are all isolated from one another.
Equally important, when its time to add the update statements, none of the existing classes need change! We code the logic to build update statements in a new subclass of Sql named UpdateSql. No other code in the system will break because of this change.
@@ -560,13 +560,13 @@ Needs will change, therefore code will change. We learned in OO 101 that there a
Dependencies upon concrete details create challenges for testing our system. If were building a Portfolio class and it depends upon an external TokyoStockExchange API to derive the portfolios value, our test cases are impacted by the volatility of such a lookup. Its hard to write a test when we get a different answer every five minutes!
Instead of designing Portfolio so that it directly depends upon TokyoStockExchange, we create an interface, StockExchange, that declares a single method:
```java
public interface StockExchange {
Money currentPrice(String symbol);
}
```
We design TokyoStockExchange to implement this interface. We also make sure that the constructor of Portfolio takes a StockExchange reference as an argument:
```java
public Portfolio {
private StockExchange exchange;
public Portfolio(StockExchange exchange) {
@@ -574,9 +574,9 @@ We design TokyoStockExchange to implement this interface. We also make sure that
}
// …
}
```
Now our test can create a testable implementation of the StockExchange interface that emulates the TokyoStockExchange. This test implementation will fix the current value for any symbol we use in testing. If our test demonstrates purchasing five shares of Microsoft for our portfolio, we code the test implementation to always return $100 per share of Microsoft. Our test implementation of the StockExchange interface reduces to a simple table lookup. We can then write a test that expects $500 for our overall portfolio value.
```java
public class PortfolioTest {
private FixedStockExchangeStub exchange;
private Portfolio portfolio;
@@ -594,7 +594,7 @@ Now our test can create a testable implementation of the StockExchange interface
Assert.assertEquals(500, portfolio.value());
}
}
```
If a system is decoupled enough to be tested in this way, it will also be more flexible and promote more reuse. The lack of coupling means that the elements of our system are better isolated from each other and from change. This isolation makes it easier to understand each element of the system.
By minimizing coupling in this way, our classes adhere to another class design principle known as the Dependency Inversion Principle (DIP).5 In essence, the DIP says that our classes should depend upon abstractions, not on concrete details.