mirror of
https://github.com/glen9527/Clean-Code-zh.git
synced 2025-12-17 10:44:21 +08:00
标题 & 代码高亮
This commit is contained in:
54
docs/ch10.md
54
docs/ch10.md
@@ -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 isn’t 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 it’s 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. It’s 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, let’s 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 it’s 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 we’re building a Portfolio class and it depends upon an external TokyoStockExchange API to derive the portfolio’s value, our test cases are impacted by the volatility of such a lookup. It’s 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.
|
||||
|
||||
Reference in New Issue
Block a user