代码整洁之道 第10章 类

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ymybxx/article/details/84038886

1.类的组织

标准的java约定:类应该从一组变量列表开始。如果有公共静态常量,应该先出现。然后是私有静态变量,以及私有实体变量。很少会有公共变量。公共函数应跟在变量列表之后。
作者建议把由某个公共函数调用的私有工具函数紧随在该公共函数后面。这符合了自顶向下的原则,让程序读起来就像一篇报纸文章。

2.类应该短小

对于类,我们通过权责衡量大小
类的名称应该描述其权责。如果无法为某个类命以精确的名称,这个类大概就太长了。类名越含混,该类越有可能拥有过多权责。例如,如果类名中包括含义模糊的词,如Processor或super,这种现象往往说明有不恰当的权责聚集情况存在

2.1 单一权责原则

单一权责认为,类或模块应有且只有一条加以修改的理由。该原则既给出权责的定义,又是关于类的长度的指导方针。类只有一个权责——只有一条修改的理由。
系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装成一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为

2.2 内聚

类应该只有少量实体变量。类中的每个方法都应该操作一个或多个这种变量。如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。

public class Stack 
{
	private int topOfStack = 0;
	List<Integer> elements = new LinkedList<Integer>();
 
	public int size() 
	{
		return topOfStack;
	}
 
	public void push(int element)
	{
		topOfStack++;
		elements.add(element);
	}
 
	public int pop() throws PoppedWhenEmpty 
	{
		if (topOfStack == 0)
			throw new PoppedWhenEmpty();
		int element = elements.get(--topOfStack);
		elements.remove(topOfStack);
			return element;
	}
}

这个类非常内聚。在三个方法中,只有size()方法没有使用所有两个变量。

2.3 保持内聚性就会得到许多短小的类

仅仅是将较大的函数切割为小函数,就将导致更多的类出现

package literatePrimes;

public class PrintPrimes {
	public static void main(String[] args) {
		final int M = 1000; 
		final int RR = 50;
		final int CC = 4;
		final int WW = 10;
		final int ORDMAX = 30; 
		int P[] = new int[M + 1]; 
		int PAGENUMBER;
		int PAGEOFFSET; 
		int ROWOFFSET; 
		int C;
		int J;
		int K;
		boolean JPRIME;
		int ORD;
		int SQUARE;
		int N;
		int MULT[] = new int[ORDMAX + 1];
		
		J = 1;
		K = 1; 
		P[1] = 2; 
		ORD = 2; 
		SQUARE = 9;
	
		while (K < M) { 
			do {
				J = J + 2;
				if (J == SQUARE) {
					ORD = ORD + 1;
					SQUARE = P[ORD] * P[ORD]; 
					MULT[ORD - 1] = J;
				}
				N = 2;
				JPRIME = true;
				while (N < ORD && JPRIME) {
					while (MULT[N] < J)
						MULT[N] = MULT[N] + P[N] + P[N];
					if (MULT[N] == J) 
						JPRIME = false;
					N = N + 1; 
				}
			} while (!JPRIME); 
			K = K + 1;
			P[K] = J;
		} 
		{
			PAGENUMBER = 1; 
			PAGEOFFSET = 1;
			while (PAGEOFFSET <= M) {
				System.out.println("The First " + M + " Prime Numbers --- Page " + PAGENUMBER);
				System.out.println("");
				for (ROWOFFSET = PAGEOFFSET; ROWOFFSET < PAGEOFFSET + RR; ROWOFFSET++) {
					for (C = 0; C < CC;C++)
						if (ROWOFFSET + C * RR <= M)
							System.out.format("%10d", P[ROWOFFSET + C * RR]); 
					System.out.println("");
				}
				System.out.println("\f"); PAGENUMBER = PAGENUMBER + 1; PAGEOFFSET = PAGEOFFSET + RR * CC;
			}
		}
	}
}

该程序只有一个大函数,它拥有很深的缩进结构,冗余的变量和紧密耦合的结构。至少应该将其拆分为数个较小的函数。

package literatePrimes;

public class PrimePrinter {
	public static void main(String[] args) {
		final int NUMBER_OF_PRIMES = 1000;
		int[] primes = PrimeGenerator.generate(NUMBER_OF_PRIMES);
		
		final int ROWS_PER_PAGE = 50; 
		final int COLUMNS_PER_PAGE = 4; 
		RowColumnPagePrinter tablePrinter = 
			new RowColumnPagePrinter(ROWS_PER_PAGE, 
						COLUMNS_PER_PAGE, 
						"The First " + NUMBER_OF_PRIMES + " Prime Numbers");
		tablePrinter.print(primes); 
	}
}
package literatePrimes;

import java.io.PrintStream;

public class RowColumnPagePrinter { 
	private int rowsPerPage;
	private int columnsPerPage; 
	private int numbersPerPage; 
	private String pageHeader; 
	private PrintStream printStream;
	
	public RowColumnPagePrinter(int rowsPerPage, int columnsPerPage, String pageHeader) { 
		this.rowsPerPage = rowsPerPage;
		this.columnsPerPage = columnsPerPage; 
		this.pageHeader = pageHeader;
		numbersPerPage = rowsPerPage * columnsPerPage; 
		printStream = System.out;
	}
	
	public void print(int data[]) { 
		int pageNumber = 1;
		for (int firstIndexOnPage = 0 ; 
			firstIndexOnPage < data.length ; 
			firstIndexOnPage += numbersPerPage) { 
			int lastIndexOnPage =  Math.min(firstIndexOnPage + numbersPerPage - 1, data.length - 1);
			printPageHeader(pageHeader, pageNumber); 
			printPage(firstIndexOnPage, lastIndexOnPage, data); 
			printStream.println("\f");
			pageNumber++;
		} 
	}
	
	private void printPage(int firstIndexOnPage, int lastIndexOnPage, int[] data) { 
		int firstIndexOfLastRowOnPage =
		firstIndexOnPage + rowsPerPage - 1;
		for (int firstIndexInRow = firstIndexOnPage ; 
			firstIndexInRow <= firstIndexOfLastRowOnPage ;
			firstIndexInRow++) { 
			printRow(firstIndexInRow, lastIndexOnPage, data); 
			printStream.println("");
		} 
	}
	
	private void printRow(int firstIndexInRow, int lastIndexOnPage, int[] data) {
		for (int column = 0; column < columnsPerPage; column++) {
			int index = firstIndexInRow + column * rowsPerPage; 
			if (index <= lastIndexOnPage)
				printStream.format("%10d", data[index]); 
		}
	}

	private void printPageHeader(String pageHeader, int pageNumber) {
		printStream.println(pageHeader + " --- Page " + pageNumber);
		printStream.println(""); 
	}
		
	public void setOutput(PrintStream printStream) { 
		this.printStream = printStream;
	} 
}
package literatePrimes;

import java.util.ArrayList;

public class PrimeGenerator {
	private static int[] primes;
	private static ArrayList<Integer> multiplesOfPrimeFactors;

	protected static int[] generate(int n) {
		primes = new int[n];
		multiplesOfPrimeFactors = new ArrayList<Integer>(); 
		set2AsFirstPrime(); 
		checkOddNumbersForSubsequentPrimes();
		return primes; 
	}

	private static void set2AsFirstPrime() { 
		primes[0] = 2; 
		multiplesOfPrimeFactors.add(2);
	}
	
	private static void checkOddNumbersForSubsequentPrimes() { 
		int primeIndex = 1;
		for (int candidate = 3 ; primeIndex < primes.length ; candidate += 2) { 
			if (isPrime(candidate))
				primes[primeIndex++] = candidate; 
		}
	}

	private static boolean isPrime(int candidate) {
		if (isLeastRelevantMultipleOfNextLargerPrimeFactor(candidate)) {
			multiplesOfPrimeFactors.add(candidate);
			return false; 
		}
		return isNotMultipleOfAnyPreviousPrimeFactor(candidate); 
	}

	private static boolean isLeastRelevantMultipleOfNextLargerPrimeFactor(int candidate) {
		int nextLargerPrimeFactor = primes[multiplesOfPrimeFactors.size()];
		int leastRelevantMultiple = nextLargerPrimeFactor * nextLargerPrimeFactor; 
		return candidate == leastRelevantMultiple;
	}
	
	private static boolean isNotMultipleOfAnyPreviousPrimeFactor(int candidate) {
		for (int n = 1; n < multiplesOfPrimeFactors.size(); n++) {
			if (isMultipleOfNthPrimeFactor(candidate, n)) 
				return false;
		}
		return true; 
	}
	
	private static boolean isMultipleOfNthPrimeFactor(int candidate, int n) {
		return candidate == smallestOddNthMultipleNotLessThanCandidate(candidate, n);
	}
	
	private static int smallestOddNthMultipleNotLessThanCandidate(int candidate, int n) {
		int multiple = multiplesOfPrimeFactors.get(n); 
		while (multiple < candidate)
			multiple += 2 * primes[n]; 
		multiplesOfPrimeFactors.set(n, multiple); 
		return multiple;
	} 
}

1.重构后的程序采用了更长,更有描述性的变量名。
2.重构后的程序将函数和类声明当做是给代码添加注释的一种手段。
3.我们采用了空格和格式技巧让程序更可读。

程序被拆分为3个主要权责:
1.PrimePrinter类中只有主程序。主程序的权责是处理执行环境。如果调用方法改变,它也会随之改变
2.RowColumnPagePrinter类懂得如何将数字列表格式化到有固定行、列数的页面上。若格式需要改动,则该类也会被影响到。
3.PrimeGenerator类懂得如何生成素数列表。如果计算素数的算法发生变动,则该类也会改动。

3.为了修改而组织

对于多数系统,修改将一直持续。每处修改都让我们冒着系统其它部分不能如期望般工作的风险。在整洁的系统中,我们对类加以组织,以降低修改的风险

public class Sql {
	public Sql(String table, Column[] columns)
	public String create()
	public String insert(Object[] fields)
	public String selectAll()
	public String findByKey(String keyColumn, String keyValue)
	public String select(Column column, String pattern)
	public String select(Criteria criteria)
	public String preparedInsert()
	private String columnList(Column[] columns)
	private String valuesList(Object[] fields, final Column[] columns) private String selectWithCriteria(String criteria)
	private String placeholderList(Column[] columns)
}

sql类用于生成提供恰当元数据的sql格式化字符串。

当增加一种新语句,或者改动单个语句类型时,都要修改sql类,这就违反了srp原则

abstract public class Sql {
		public Sql(String table, Column[] columns) 
		abstract public String generate();
	}
	public class CreateSql extends Sql {
		public CreateSql(String table, Column[] columns) 
		@Override public String generate()
	}
	
	public class SelectSql extends Sql {
		public SelectSql(String table, Column[] columns) 
		@Override public String generate()
	}
	
	public class InsertSql extends Sql {
		public InsertSql(String table, Column[] columns, Object[] fields) 
		@Override public String generate()
		private String valuesList(Object[] fields, final Column[] columns)
	}
	
	public class SelectWithCriteriaSql extends Sql { 
		public SelectWithCriteriaSql(
		String table, Column[] columns, Criteria criteria) 
		@Override public String generate()
	}
	
	public class SelectWithMatchSql extends Sql { 
		public SelectWithMatchSql(String table, Column[] columns, Column column, String pattern) 
		@Override public String generate()
	}
	
	public class FindByKeySql extends Sql public FindByKeySql(
		String table, Column[] columns, String keyColumn, String keyValue) 
		@Override public String generate()
	}
	
	public class PreparedInsertSql extends Sql {
		public PreparedInsertSql(String table, Column[] columns) 
		@Override public String generate() {
		private String placeholderList(Column[] columns)
	}
	
	public class Where {
		public Where(String criteria) public String generate()
	}
	
	public class ColumnList {
		public ColumnList(Column[] columns) public String generate()
	}

每个类中的代码变得极为简单。理解每个类花费的时间缩减到零。函数对其它函数造成毁坏的风险也变得几近于无。

当需要增加update语句时,现存类不许做任何修改,系统中的其他代码都不会因为这个修改而被破坏。

重构后的程序支持SRP,同时支持开放-闭合原则(OCP):类应该对扩展开放,对修改封闭

隔离修改

依赖于具体细节的客户类,当细节改变时,就会有风险。我们可以借助接口和抽象类来隔离细节带来的影响。

与其设计直接依赖于TokyoStockExchange的Portfolio类,不如创建StockExchange接口,其中只声明一个方法:

public interface StockExchange { 
	Money currentPrice(String symbol);
}

设计TokyoStockEchange类来实现这个接口。我们还要确保Portfolio的构造器接受作为参数的StockEchange引用

public Portfolio {
	private StockExchange exchange;
	public Portfolio(StockExchange exchange) {
		this.exchange = exchange; 
	}
}

部件之间的解耦代表着系统中的元素相隔离的很好。
依赖导致原则(DIP):
DIP认为类应当依赖于抽象而不是依赖于具体细节

猜你喜欢

转载自blog.csdn.net/ymybxx/article/details/84038886
今日推荐