Presentation is loading. Please wait.

Presentation is loading. Please wait.

第九讲 重构到模式. 什么是重构( Refactoring ) 所谓重构是这样一个过程:在不改变代码外在行为的 前提下,对代码做出修改,以改进程序的内部结构。

Similar presentations


Presentation on theme: "第九讲 重构到模式. 什么是重构( Refactoring ) 所谓重构是这样一个过程:在不改变代码外在行为的 前提下,对代码做出修改,以改进程序的内部结构。"— Presentation transcript:

1 第九讲 重构到模式

2 什么是重构( Refactoring ) 所谓重构是这样一个过程:在不改变代码外在行为的 前提下,对代码做出修改,以改进程序的内部结构。

3 Extract Method 名称: Extract Method 动机:过长的函数或者一段需要注释才能让人理解用途 的代码

4 Extract Method 做法: 创造一个新函数,根据这个函数的意图来给它命名(以它做什么来命名,而不是以 它怎样做命名) 将提炼出的代码从源函数拷贝到新建的目标函数中 仔细检查提炼出的代码,看看其中是否引用了作用域限于源函数的变量(包括局部 变量和源函数参数) 检查是否有仅用于被提炼码的临时变量,如果有,在目标函数中将它们声明为临时 变量 检查被提炼码,看看是否有任何局部变量的值被它改变,如果一个临时变量值被修 改了,看看是否可以将被提炼码处理为一个查询,并将结果赋值给相关变量,如果 很难这样做,或如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动地 提炼出来,你可能需要先使用 Split Temporary Variable ,然后再尝试提炼,也可以 使用 Replace Temp with Query 将临时变量消灭掉 将被提炼码中需要读取的局部变量,当作参数传给目标函数 处理完所有局部变量之后,进行编译 在源函数中,将被提炼码替换为对目标函数的调用 编译,测试

5 Extract Method 范例: void printOwing(double amount) { printBanner(); //print details System.out.println ("name:" + _name); System.out.println ("amount" + amount); } ↓ void printOwing(double amount) { printBanner(); printDetails(amount); } void printDetails (double amount) { System.out.println ("name:" + _name); System.out.println ("amount" + amount); }

6 重构 - 示例 这是一个影碟出租店用的程序,计算每一位顾客的消 费金额并打印报表( Statement )。操作者告诉程序: 顾客租了哪些影片、租期多长,程序便根据租赁时间 和影片类型算出费用。影片分为三类:普通片、儿童 片和新片。除了计算费用,还要为顾客计算点数;点 数会随着租片种类是否为新片而有所不同。

7 重构 - 示例

8 Movie Movie 只是一个简单的 data class (纯数据类) public class Movie { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String _title; private int _priceCode; public Movie(String title, int priceCode) { _title = title; _priceCode = priceCode; } public int getPriceCode() { return _priceCode; } public void setPriceCode(int arg) { _priceCode = arg; } public String getTitle (){ return _title; }; }

9 Rental Rental class 表示 某个顾客租了一部影片 class Rental { private Movie _movie; private int _daysRented; public Rental(Movie movie, int daysRented) { _movie = movie; _daysRented = daysRented; } public int getDaysRented() { return _daysRented; } public Movie getMovie() { return _movie; }

10 Customer Customer class 用来表示顾客,就像其他 classes 一样,它也拥有数据和相 应的访问函数( accessor ): class Customer { private String _name; private Vector _rentals = new Vector(); public Customer (String name){ _name = name; }; public void addRental(Rental arg) { _rentals.addElement(arg); } public String getName (){ return _name; };

11 Customer 提供报表方法的交互过 程

12 报表相应代码 public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); //determine amounts for each line switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) 16 thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; }

13 起始程序的讨论 问题:考虑一下增加 HTML 格式打印报表的需求;改 变影片分类规则 如果你发现自己需要为程序添加一个特性,而代码结 构使你无法很方便地那么做,那就先重构那个程序, 使特性的添加比较容易进行,然后再添加特性

14 重构的第一步 重构的第一个步骤永远相同:为即将修改的代码建立 一组可靠的测试环境。这些测试必须有自我检测( self-checking )能力。

15 分解并重组 statement() 第一个步骤是找出代码的逻辑泥团( logical clump ) 并运用 Extract Method

16 修改后的 statement 方法 public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); thisAmount = amountFor(each); // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental 20 result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } private double amountFor(Rental each) { double thisAmount = 0; switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; 21 break; } return thisAmount; }

17 改名后的代码 private double amountFor(Rental aRental) { double result = 0; switch (aRental.getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (aRental.getDaysRented() > 2) result += (aRental.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: result += aRental.getDaysRented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (aRental.getDaysRented() > 3) result += (aRental.getDaysRented() - 3) * 1.5; break; } return result; }

18 转移 金额计算 代码 通过观察 amountFor(), 可以发现这个函数使用了来自 Rental class 信息,却没有使用来自 customer class 的信息 private double amountFor(Rental aRental) { double result = 0; switch (aRental.getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (aRental.getDaysRented() > 2) result += (aRental.getDaysRented() - 2) * 1.5; 23 break; case Movie.NEW_RELEASE: result += aRental.getDaysRented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (aRental.getDaysRented() > 3) result += (aRental.getDaysRented() - 3) * 1.5; break; } return result; }

19 Move Method Public double getCharge() { double result = 0; switch (getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (getDaysRented() > 2) result += (getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: result += getDaysRented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (getDaysRented() > 3) result += (getDaysRented() - 3) * 1.5; break; } return result; }

20 Move Method Customer.amountFor() 函数内的处理,使它委托( delegate) 新函数: private double amountFor(Rental aRental) { return aRental.getCharge(); }

21 Move Method 下一步 找出程序对于旧函数的所有引用点,并修改它们让它们改用新函数 class Customer public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); thisAmount = each.getCharge(); // add frequent renter points 25 frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; }

22 转移金额计算函数后,所有 classes 的状态( state )

23 Replace Temp with Query public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf (each.getCharge()) + "\n"; totalAmount += each.getCharge(); } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; }

24 提炼顾客积分计算代码 public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // add frequent renter points frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(each.getCharge()) + "\n"; totalAmount += each.getCharge(); } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; }

25 提炼后代码 public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints(); //show figures for this rental result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf(each.getCharge()) + "\n"; totalAmount += each.getCharge(); } //add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } class Rental... int getFrequentRenterPoints() { if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1) return 2; else return 1; }

26 顾客积分计算函数提炼及转移后

27

28 结论 重构技术系以微小的步伐修改程序。如果你犯下错误 ,很容易便可发现它。 写出人类容易理解的代码,才是优秀的程序员

29 重构 重构:对软件内部结构的一种调整,目的是在不改变 软件的可观察行为前提下,提高其可理解性,降低其 修改成本 一般而言重构都是对软件的小改动,但重构可以包含 另一个重构。例如 Extract Class 通常包含 Move Method 和 Move Field

30 为何重构 重构可以改进软件设计。重构就像是在整理代码,让 所有东西回到应该的位置上。 重构使软件更易被理解 重构可以帮助寻找 bugs 重构帮助提高编程速度。良好的设计是快速软件开发 的根本,恶劣的设计会是开发速度下降,引起耗时的 调试,增加新功能,维护等。

31 何时重构 三次法则( The Rule of Three ) 第一次做某件事时只管去做;第二次做类似的事会产生反 感,但无论如何还是做了;第三次再做类似的事,你就 应该重构!

32 Design SmellsThe Odors of Rotting Software The software is rotting when it starts to exhibit any of the following odors Rigidity Fragility Immobility Viscosity Needless complexity Needless repetition Opacity

33 Rigidity Rigidity is the tendency for software to be difficult to change, even in simple ways. A design is rigid if a single change causes a cascade of subsequent changes in dependent modules. The more modules that must be changed, the more rigid the design. Most developers have faced this situation in one way or another. They are asked to make what appears to be a simple change. They look the change over and make a reasonable estimate of the work required. But later, as they work through the change, they find that there are unanticipated repercussions to the change. The developers find themselves chasing the change through huge portions of the code, modifying far more modules than they had first estimated, and discovering thread after thread of other changes that they must remember to make. In the end, the changes take far longer than the initial estimate. When asked why their estimate was so poor, they repeat the traditional software developers lament: "It was a lot more complicated than I thought!"

34 Fragility Fragility is the tendency of a program to break in many places when a single change is made. Often, the new problems are in areas that have no conceptual relationship with the area that was changed. Fixing those problems leads to even more problems, and the development team begins to resemble a dog chasing its tail. As the fragility of a module increases, the likelihood that a change will introduce unexpected problems approaches certainty. This seems absurd, but such modules are not at all uncommon. These are the modules that are continually in need of repair, the ones that are never off the bug list. These modules are the ones that the developers know need to be redesigned, but nobody wants to face the spectre of redesigning them. These modules are the ones that get worse the more you fix them.

35 Immobility A design is immobile when it contains parts that could be useful in other systems, but the effort and risk involved with separating those parts from the original system are too great. This is an unfortunate but very common occurrence.

36 Viscosity Viscosity comes in two forms: viscosity of the software and viscosity of the environment. When faced with a change, developers usually find more than one way to make that change. Some of the ways preserve the design; others do not (i.e., they are hacks). When the design-preserving methods are more difficult to use than the hacks, the viscosity of the design is high. It is easy to do the wrong thing but difficult to do the right thing. We want to design our software such that the changes that preserve the design are easy to make. Viscosity of environment comes about when the development environment is slow and inefficient. For example, if compile times are very long, developers will be tempted to make changes that don't force large recompiles, even though those changes don't preserve the design. If the source code control system requires hours to check in just a few files, developers will be tempted to make changes that require as few check-ins as possible, regardless of whether the design is preserved. In both cases, a viscous project is one in which the design of the software is difficult to preserve. We want to create systems and project environments that make it easy to preserve and improve the design.

37 Needless Complexity A design smells of needless complexity when it contains elements that aren't currently useful. This frequently happens when developers anticipate changes to the requirements and put facilities in the software to deal with those potential changes. At first, this may seem like a good thing to do. After all, preparing for future changes should keep our code flexible and prevent nightmarish changes later. Unfortunately, the effect is often just the opposite. By preparing for many contingencies, the design becomes littered with constructs that are never used. Some of those preparations may pay off, but many more do not. Meanwhile, the design carries the weight of these unused design elements. This makes the software complex and difficult to understand.

38 Needless Repetition Cut and paste may be useful text-editing operations, but they can be disastrous code-editing operations. All too often, software systems are built on dozens or hundreds of repeated code elements. It happens like this: Ralph needs to write some code that fravles the arvadent.[2] He looks around in other parts of the code where he suspects other arvadent fravling has occurred and finds a suitable stretch of code. He cuts and pastes that code into his module and makes the suitable modifications. [2] For those of you who do not have English as your first language, the term fravle the arvadent is composed of nonsense words and is meant to imply some nondescript programming activity. Unbeknownst to Ralph, the code he scraped up with his mouse was put there by Todd, who scraped it out of a module written by Lilly. Lilly was the first to fravle an arvadent, but she realized that fravling an arvadent was very similar to fravling a garnatosh. She found some code somewhere that fravled a garnatosh, cut and paste it into her module, and modified it as necessary. When the same code appears over and over again, in slightly different forms, the developers are missing an abstraction. Finding all the repetition and eliminating it with an appropriate abstraction may not be high on their priority list, but it would go a long way toward making the system easier to understand and maintain. When there is redundant code in the system, the job of changing the system can become arduous. Bugs found in such a repeating unit have to be fixed in every repetition. However, since each repetition is slightly different from every other, the fix is not always the same.

39 Opacity Opacity is the tendency of a module to be difficult to understand. Code can be written in a clear and expressive manner, or it can be written in an opaque and convoluted manner. Code that evolves over time tends to become more and more opaque with age. A constant effort to keep the code clear and expressive is required in order to keep opacity to a minimum. When developers first write a module, the code may seem clear to them. After all, they have immersed themselves in it and understand it at an intimate level. Later, after the intimacy has worn off, they may return to that module and wonder how they could have written anything so awful. To prevent this, developers need to put themselves in their readers' shoes and make a concerted effort to refactor their code so that their readers can understand it. They also need to have their code reviewed by others.

40 Why Software Rots In nonagile environments, designs degrade because requirements change in ways that the initial design did not anticipate. Often, these changes need to be made quickly and may be made by developers who are not familiar with the original design philosophy. So, though the change to the design works, it somehow violates the original design. Bit by bit, as the changes continue, these violations accumulate until malignancy sets in. However, we cannot blame the drifting of the requirements for the degradation of the design. We, as software developers, know full well that requirements change. Indeed, most of us realize that the requirements are the most volatile elements in the project. If our designs are failing owing to the constant rain of changing requirements, it is our designs and practices that are at fault. We must somehow find a way to make our designs resilient to such changes and use practices that protect them from rotting. An agile team thrives on change. The team invests little up front and so is not vested in an aging initial design. Rather, the team keeps the design of the system as clean and simple as possible and backs it up with lots of unit tests and acceptance tests. This keeps the design flexible and easy to change. The team takes advantage of that flexibility in order to continuously improve the design; thus, each iteration ends with a system whose design is as appropriate as it can be for the requirements in that iteration.

41 例子 Copy program structure chart

42 There are three modules, or subprograms, in the application. The Copy module calls the other two. The Copy program fetches characters from the Read Keyboard module and routes them to the Write Printer module. public class Copier { public static void Copy() { int c; while((c=Keyboard.Read()) != -1) Printer.Write(c); }

43 First modification of Copy program public class Copier { //remember to reset this flag public static bool ptFlag = false; public static void Copy() { int c; while((c=(ptFlag ? PaperTape.Read() : Keyboard.Read())) != -1) Printer.Write(c); }

44 Second modification of Copy program public class Copier { //remember to reset these flags public static bool ptFlag = false; public static bool punchFlag = false; public static void Copy() { int c; while((c=(ptFlag ? PaperTape.Read() : Keyboard.Read())) != -1) punchFlag ? PaperTape.Punch(c) : Printer.Write(c); }

45 Agile version 2 of Copy public interface Reader { int Read(); } public class KeyboardReader : Reader { public int Read() {return Keyboard.Read();} } public class Copier { public static Reader reader = new KeyboardReader(); public static void Copy() { int c; while((c=(reader.Read())) != -1) Printer.Write(c); }

46 单一职责原则 (SRP) 就一个类而言,应该仅有一个引起它变化的原因。

47 More than one responsibility

48 Separated responsibilities

49 Defining a Responsibility public interface Modem { public void Dial(string pno); public void Hangup(); public void Send(char c); public char Recv(); }

50

51 Separating Coupled Responsibilities I kept both responsibilities coupled in the ModemImplementation class. This is not desirable, but it may be necessary. There are often reasons, having to do with the details of the hardware or operating system, that force us to couple things that we'd rather not couple. However, by separating their interfaces, we have decoupled the concepts as far as the rest of the application is concerned. We may view the ModemImplementation class as a kludge or a wart; however, note that all dependencies flow away from it. Nobody needs to depend on this class. Nobody except main needs to know that it exists. Thus, we've put the ugly bit behind a fence. Its ugliness need not leak out and pollute the rest of the application.

52 Coupled persistence

53 Conclusion The Single-Responsibility Principle is one of the simplest of the principles but one of the most difficult to get right. Conjoining responsibilities is something that we do naturally. Finding and separating those responsibilities is much of what software design is really about. Indeed, the rest of the principles we discuss come back to this issue in one way or another.

54 开放 - 封闭原则 (OCP) 软件实体(类、模块、函数等)应该是可以扩展的,但是不可修 改。 When a single change to a program results in a cascade of changes to dependent modules, the design smells of rigidity. OCP advises us to refactor the system so that further changes of that kind will not cause more modifications. If OCP is applied well, further changes of that kind are achieved by adding new code, not by changing old code that already works. This may seem like motherhood and apple piethe golden, unachievable idealbut in fact, there are some relatively simple and effective strategies for approaching that ideal.

55 Description of OCP 1. They are open for extension. This means that the behavior of the module can be extended. As the requirements of the application change, we can extend the module with new behaviors that satisfy those changes. In other words, we are able to change what the module does. 2. They are closed for modification. Extending the behavior of a module does not result in changes to the source, or binary, code of the module. The binary executable version of the modulewhether in a linkable library, a DLL, or a.EXE fileremains untouched.

56 Client is not open and closed.

57 STRATEGY pattern: Client is both open and closed.

58 TEMPLATE METHOD pattern: Base class is open and closed.

59 Conclusion In many ways, the Open/Closed Principle is at the heart of object- oriented design. Conformance to this principle is what yields the greatest benefits claimed for object-oriented technology: flexibility, reusability, and maintainability. Yet conformance to this principle is not achieved simply by using an object-oriented programming language. Nor is it a good idea to apply rampant abstraction to every part of the application. Rather, it requires a dedication on the part of the developers to apply abstraction only to those parts of the program that exhibit frequent change. Resisting premature abstraction is as important as abstraction itself.

60 Liskov 替换原则 (LSP) 子类型必须能够替换掉它们的基类型。 The importance of this principle becomes obvious when you consider the consequences of violating it. Presume that we have a function f that takes as its argument a reference to some base class B. Presume also that when passed to f in the guise of B, some derivative D of B causes f to misbehave. Then D violates LSP. Clearly, D is fragile in the presence of f. The authors of f will be tempted to put in some kind of test for D so that f can behave properly when a D is passed to it. This test violates OCP because now, f is not closed to all the various derivatives of B. Such tests are a code smell that are the result of inexperienced developers or, what's worse, developers in a hurry reacting to LSP violations.

61 Conclusion The Open/Closed Principle is at the heart of many of the claims made for object- oriented design. When this principle is in effect, applications are more maintainable, reusable, and robust. The Liskov Substitution Principle is one of the prime enablers of OCP. The substitutability of subtypes allows a module, expressed in terms of a base type, to be extensible without modification. That substitutability must be something that developers can depend on implicitly. Thus, the contract of the base type has to be well and prominently understood, if not explicitly enforced, by the code. The term IS-A is too broad to act as a definition of a subtype. The true definition of a subtype is substitutable, where substitutability is defined by either an explicit or implicit contract.

62 依赖反转原则 (DIP) High-level modules should not depend on low-level modules. Both should depend on abstractions. 抽象不应该依赖于细节。细节应该依赖于抽象

63 Layering According to Booch, "all well structured object-oriented architectures have clearly- defined layers, with each layer providing some coherent set of services through a well-defined and controlled interface."[1] A naive interpretation of this statement might lead a designer to produce a structure similar to Figure 11-1. In this diagram, the high-level Policy layer uses a lower-level Mechanism layer, which in turn uses a detailed-level Utility layer. Although this may look appropriate, it has the insidious characteristic that the Policy layer is sensitive to changes all the way down in the Utility layer. Dependency is transitive. The Policy layer depends on something that depends on the Utility layer; thus, the Policy layer transitively depends on the Utility layer. This is very unfortunate.

64 Naive layering scheme Figure

65 Inverted layers

66 Dependence on Abstractions No variable should hold a reference to a concrete class. No class should derive from a concrete class. No method should override an implemented method of any of its base classes.

67 Naive model of a Button and a Lamp

68 Dependency inversion applied to Lamp

69 Generic regulator

70 Conclusion Traditional procedural programming creates a dependency structure in which policy depends on detail. This is unfortunate, since the policies are then vulnerable to changes in the details. Objectoriented programming inverts that dependency structure such that both details and policies depend on abstraction, and service interfaces are often owned by their clients. Indeed, this inversion of dependencies is the hallmark of good object-oriented design. It doesn't matter what language a program is written in. If its dependencies are inverted, it has an OO design. If its dependencies are not inverted, it has a procedural design. The principle of dependency inversion is the fundamental low-level mechanism behind many of the benefits claimed for object-oriented technology. Its proper application is necessary for the creation of reusable frameworks. It is also critically important for the construction of code that is resilient to change. Since abstractions and details are isolated from each other, the code is much easier to maintain.

71 接口隔离原则 (ISP) 不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于 它所在的类层次结构。 This principle deals with the disadvantages of "fat" interfaces. Classes whose interfaces are not cohesive have "fat" interfaces. In other words, the interfaces of the class can be broken up into groups of methods. Each group serves a different set of clients. Thus, some clients use one group of methods, and other clients use the other groups. ISP acknowledges that there are objects that require noncohesive interfaces; however, it suggests that clients should not know about them as a single class. Instead, clients should know about abstract base classes that have cohesive interfaces.

72 TimerClient at top of hierarchy The

73 The Interface Segregation Principle Clients should not be forced to depend on methods they do not use. When clients are forced to depend on methods they don't use, those clients are subject to changes to those methods. This results in an inadvertent coupling between all the clients. Said another way, when a client depends on a class that contains methods that the client does not use but that other clients do use, that client will be affected by the changes that those other clients force on the class. We would like to avoid such couplings where possible, and so we want to separate the interfaces.

74 重用发布等价原则( REP ) 重用的粒度就是发布的粒度

75 共同封闭原则( CCP ) 包中的所有类对于同一类性质的变化应该是共同封闭 的。一个变化若对一个包产生影响,则将对该包中的 所有类产生影响,而对于其他的包不造成任何影响

76 共同重用原则( CRP ) 一个包中的所有类应该是共同重用的。如果重用了包 中的一个类,那么就要重用包中的所有类

77 无环依赖原则( ADP ) 在包的依赖关系图中不允许存在环

78 稳定依赖原则( SDP ) 朝着稳定的方向进行依赖

79 稳定抽象原则( SAP ) 包的抽象程度应该和其稳定程度一致

80 谢谢大家! 谢谢大家!


Download ppt "第九讲 重构到模式. 什么是重构( Refactoring ) 所谓重构是这样一个过程:在不改变代码外在行为的 前提下,对代码做出修改,以改进程序的内部结构。"

Similar presentations


Ads by Google