Lớp này không làm bất cứ điều gì, nhưng bạn có thể tạo ra một đối tượng: DataOnly d = mới DataOnly (); Cả hai classname và các lĩnh vực ngoại trừ s trước công chúng từ. Điều này có nghĩa rằng họ có thể nhìn thấy tất cả các đối tượng khác. Bạn có thể gán giá trị cho các thành viên dữ liệu có thể nhìn thấy, nhưng trước tiên bạn phải biết làm thế nào để tham khảo cho một thành viên của một đối tượng. Điều này được thực hiện bằng cách nêu rõ tên của...
But be careful with your assumptions In general, it’s difficult to anticipate how a class can be reused, especially a general-purpose class Unless you declare a method as virtual, you prevent the possibility of reusing your class through inheritance in some other programmer’s project simply because you couldn’t imagine it being used that way Initialization and class loading In more traditional languages, programs are loaded all at once as part of the startup process This is followed by initialization, and then the program begins The process of initialization in these languages must be carefully controlled so that the order of initialization of statics doesn’t cause trouble C++, for example, has problems if one static expects another static to be valid before the second one has been initialized C# doesn’t have this problem because it takes a different approach to loading Because everything in C# is an object, many activities become easier, and this is one of them As you will learn more fully in the next chapter, the compiled code for a set of related classes exists in their own separate file, called an assembly That file isn’t loaded until the code is needed In general, you can say that “Class code is loaded at the point of first use.” This is often not until the first object of that class is constructed, but loading also occurs when a static field or static method is accessed The point of first use is also where the static initialization takes place All the static objects and the static code block will be initialized in textual order (that is, the order that you write them down in the class definition) at the point of loading The statics, of course, are initialized only once Initialization with inheritance It’s helpful to look at the whole initialization process, including inheritance, to get a full picture of what happens Consider the following code: //:c07:Beetle.cs // The full process of initialization using System; class Insect { int i = 9; internal int j; Chapter 7: Reusing Classes 255 internal Insect() { Prt("i = " + i + ", j = " + j); j = 39; } static int x1 = Prt("static Insect.x1 initialized"); internal static int Prt(string s) { Console.WriteLine(s); return 47; } } class Beetle : Insect { int k = Prt("Beetle.k initialized"); Beetle() { Prt("k = " + k); Prt("j = " + j); } static int x2 = Prt("static Beetle.x2 initialized"); public static void Main() { Prt("Beetle constructor"); Beetle b = new Beetle(); } } ///:~ The output for this program is: static Insect.x1 initialized static Beetle.x2 initialized Beetle constructor Beetle.k initialized i = 9, j = k = 47 j = 39 The first thing that happens when you run Beetle is that you try to access Beetle.Main( ) (a static method), so the loader goes out and finds the compiled code for the Beetle class (this happens to be in an assembly called Beetle.exe) In the process of loading it, the loader notices that it has a base class (that’s what the colon after class Beetle says), which it then loads This will happen whether 256 Thinking in C# www.MindView.net or not you’re going to make an object of that base class (Try commenting out the object creation to prove it to yourself.) If the base class has a base class, that second base class would then be loaded, and so on Next, the static initialization in the root base class (in this case, Insect) is performed, and then the next derived class, and so on This is important because the derived-class static initialization might depend on the base class member being initialized properly At this point, the necessary classes have all been loaded so the object can be created First, all the primitives in this object are set to their default values and the object references are set to null—this happens in one fell swoop by setting the memory in the object to binary zero Then, the base-class fields are initialized in textual order, followed by the fields of the object After the fields are initialized, the base-class constructor will be called In this case the call is automatic, but you can also specify the base-class constructor call (by placing a color after the Beetle( ) constructor and then saying base( )) The base class construction goes through the same process in the same order as the derived-class constructor Finally, the rest of the body of the constructor is executed Summary Both inheritance and composition allow you to create a new type from existing types Typically, however, you use composition to reuse existing types as part of the underlying implementation of the new type, and inheritance when you want to reuse the interface Since the derived class has the base-class interface, it can be upcast to the base, which is critical for polymorphism, as you’ll see in the next chapter Despite the strong emphasis on inheritance in object-oriented programming, when you start a design you should generally prefer composition during the first cut and use inheritance only when it is clearly necessary Composition tends to be more flexible In addition, by using the added artifice of inheritance with your member type, you can change the exact type, and thus the behavior, of those member objects at run-time Therefore, you can change the behavior of the composed object at run-time Although code reuse through composition and inheritance is helpful for rapid project development, you’ll generally want to redesign your class hierarchy before allowing other programmers to become dependent on it Your goal is a hierarchy in which each class has a specific use and is neither too big (encompassing so much functionality that it’s unwieldy to reuse) nor annoyingly small (you can’t use it by itself or without adding functionality) Chapter 7: Reusing Classes 257 Exercises 258 Create two classes, A and B, with default constructors (empty argument lists) that announce themselves Inherit a new class called C from A, and create a member of class B inside C Do not create a constructor for C Create an object of class C and observe the results Modify Exercise so that A and B have constructors with arguments instead of default constructors Write a constructor for C and perform all initialization within C’s constructor Create a simple class Inside a second class, define a field for an object of the first class Use lazy initialization to instantiate this object Inherit a new class from class Detergent Override Scrub( ) and add a new method called Sterilize( ) Take the file Cartoon.cs and comment out the constructor for the Cartoon class Explain what happens Take the file Chess.cs and comment out the constructor for the Chess class Explain what happens Prove that default constructors are created for you by the compiler Prove that the base-class constructors are (a) always called, and (b) called before derived-class constructors Create a base class with only a nondefault constructor, and a derived class with both a default and nondefault constructor In the derived-class constructors, call the base-class constructor 10 Create a class called Root that contains an instance of each of classes (that you also create) named Component1, Component2, and Component3 Derive a class Stem from Root that also contains an instance of each “component.” All classes should have default constructors that print a message about that class 11 Modify Exercise 10 so that each class only has nondefault constructors 12 Add a proper hierarchy of Dispose( ) methods to all the classes in Exercise 11 Thinking in C# www.ThinkingIn.NET 13 Create a class with a method that is overloaded three times Inherit a new class, add a new overloading of the method, and show that all four methods are available in the derived class 14 In Car.cs add a Service( ) method to Engine and call this method in Main( ) 15 Create a class inside a namespace Your class should contain a protected method and a protected internal method Compile this class into a library assembly Write a new class that tries to call these methods; compile this class into an executable assembly (you’ll need to reference the library assembly while compiling, of course) Explain the results Now inherit from your first class and call the protected and protected internal methods from this derived class Compile this derived class into its own assembly and explain the resulting behavior 16 Create a class called Amphibian From this, inherit a class called Frog Put appropriate methods in the base class In Main( ), create a Frog and upcast it to Amphibian, and demonstrate that all the methods still work 17 Modify Exercise 16 so that Frog overrides the method definitions from the base class (provides new definitions using the same method signatures) Note what happens in Main( ) 18 Create a class with a method that is not defined as virtual Inherit from that class and attempt to override that method 19 Create a sealed class and attempt to inherit from it 20 Prove that class loading takes place only once Prove that loading may be caused by either the creation of the first instance of that class, or the access of a static member 21 In Beetle.cs, inherit a specific type of beetle from class Beetle, following the same format as the existing classes Trace and explain the output 22 Find a way where inheritance can be used fruitfully in the party domain Implement at least one program that solves a problem by upcasting 23 Draw a UML class diagram of the party domain, showing inheritance and composition Place classes that interact often near each other and classes in different namespaces far apart or even on separate pieces of paper Chapter 7: Reusing Classes 259 Consider the task of ensuring that all guests are given a ride home by someone sober or given a place to sleep over Add classes, namespaces, methods, and data as appropriate 24 Consider how you would approach the tasks that you have solved in the party domain in the programming language other than C#, with which you are most familiar Fill in this Venn diagram comparing aspects of the C# approach with how you would it otherwise: Unique to other Unique to C# Similar 260 ♦ Are there aspects unique to one approach that you see as having a major productivity impact? ♦ What are some important aspects that both approaches share? Thinking in C# www.MindView.net 8: Interfaces and Implementation Polymorphism is the next essential feature of an objectoriented programming language after data abstraction It allows programs to be developed in the form of interacting agreements or “contracts” that specify the behavior, but not the implementation, of classes Polymorphism provides a dimension of separation of interface from implementation, to decouple what from how Polymorphism allows improved code organization and readability as well as the creation of extensible programs that can be “grown” not only during the original creation of the project but also when new features are desired Encapsulation creates new data types by combining characteristics and behaviors Implementation hiding separates the interface from the implementation by making the details private This sort of mechanical organization makes ready sense to someone with a procedural programming background But polymorphism deals with decoupling in terms of types In the last chapter, you saw how inheritance allows the treatment of an object as its own type or its base type This ability is critical because it allows many types (derived from the same base type) to be treated as if they were one type, and a single piece of code to work on all those different types equally The polymorphic method call allows one type to express its distinction from another, similar type, as long as they’re both derived from the same base type This distinction is expressed through differences in behavior of the methods that you can call through the base class In this chapter, you’ll learn about polymorphism (also called dynamic binding or late binding or run-time binding) starting from the basics, with simple examples that strip away everything but the polymorphic behavior of the program 261 Upcasting revisited In Chapter you saw how an object can be used as its own type or as an object of its base type Taking an object reference and treating it as a reference to its base type is called upcasting, because of the way inheritance trees are drawn with the base class at the top You also saw a problem arise, which is embodied in the following: //:c08:Music.cs // Inheritance & upcasting using System; public class Note { private int value; private Note(int val) { value = val;} public static Note MIDDLE_C = new Note(0), C_SHARP = new Note(1), B_FLAT = new Note(2); } // Etc public class Instrument { public virtual void Play(Note n) { Console.WriteLine("Instrument.Play()"); } } // Wind objects are instruments // because they have the same interface: public class Wind : Instrument { // Redefine interface method: public override void Play(Note n) { Console.WriteLine("Wind.Play()"); } } public class Music { public static void Tune(Instrument i) { // i.Play(Note.MIDDLE_C); } 262 Thinking in C# www.ThinkingIn.NET public static void Main() { Wind flute = new Wind(); Tune(flute); // Upcasting } } ///:~ The method Music.Tune( ) accepts an Instrument reference, but also anything derived from Instrument In Main( ), you can see this happening as a Wind reference is passed to Tune( ), with no cast necessary This is acceptable; the interface in Instrument must exist in Wind, because Wind is inherited from Instrument Upcasting from Wind to Instrument may “narrow” that interface, but it cannot make it anything less than the full interface to Instrument Forgetting the object type This program might seem strange to you Why should anyone intentionally forget the type of an object? This is what happens when you upcast, and it seems like it could be much more straightforward if Tune( ) simply takes a Wind reference as its argument This brings up an essential point: If you did that, you’d need to write a new Tune( ) for every type of Instrument in your system Suppose we follow this reasoning and add Stringed and Brass instruments: //:c08:Music2.cs // Overloading instead of upcasting using System; class Note { private int value; private Note(int val) { value = val;} public static readonly Note MIDDLE_C = new Note(0), C_SHARP = new Note(1), B_FLAT = new Note(2); } // Etc class Instrument { internal virtual void Play(Note n) { Console.WriteLine("Instrument.Play()"); } } class Wind : Instrument { Chapter 8: Interfaces and Implementation 263 internal override void Play(Note n) { Console.WriteLine("Wind.Play()"); } } class Stringed : Instrument { internal override void Play(Note n) { Console.WriteLine("Stringed.Play()"); } } class Brass : Instrument { internal override void Play(Note n) { Console.WriteLine("Brass.Play()"); } } public class Music2 { internal static void Tune(Wind i) { i.Play(Note.MIDDLE_C); } internal static void Tune(Stringed i) { i.Play(Note.MIDDLE_C); } internal static void Tune(Brass i) { i.Play(Note.MIDDLE_C); } public static void Main() { Wind flute = new Wind(); Stringed violin = new Stringed(); Brass frenchHorn = new Brass(); Tune(flute); // No upcasting Tune(violin); Tune(frenchHorn); } } ///:~ This works, but there’s a major drawback: You must write type-specific methods for each new Instrument class you add This means more programming in the first place, but it also means that if you want to add a new method like Tune( ) or a new type of Instrument, you’ve got a lot of work to Add the fact that the 264 Thinking in C# www.MindView.net } void PutOnTires(Color bodyColor){ // etc PaintBody(bodyColor); } void PaintBody(Color bodyColor){ //Finally use the bodyColor! } public static void Main(){ Car c = new Car(); c.BuildCar(Color.Red); } }///:~ Tramp coupling is confusing because a method’s parameters should all have importance If a Color is passed to InstallEngine( ), the implication is that the Engine relies on the Color (it’s either stamp or data coupled to the method that calls InstallEngine) Instead of using tramp coupling, the Car class should have a field that sets the desired body color when it is first calculated and PaintBody( ) should just read the value of the field This introduces state into the equation – the behavior of Car.PaintBody( ) relies on calls that are made at another point in Car’s lifecyle There’s nothing wrong with state, so long as your object is never in an invalid state; constructors and field initializers give you the tools to ensure that your fields always have some reasonable default value Control coupling If the logic of class B is controlled by a parameter given it by class A, the two classes are control-coupled The crucial concept is that the class A determines the logic that class B will use If class B makes its own decision, it is not control coupling class CalendarPrint{ public void ControlCoupledFeb (DayOfWeek firstIsOn, bool isLeap){ //etc } public void JustDataCoupledFeb(int year){ //Could calc day of week of the first, leap year } Chapter 9: Coupling and Cohesion 327 } For this Calendar-printing class to print February, it needs to know what day of the week the first falls on and whether or not the year is a leap-year These two values could be passed in, as they are in this example’s CalendarPrint.ControlCoupledFeb( ) method Imagine the code that is needed to call this: //:c09:ControlCoupling.cs class CalendarPrint{ public void ControlCoupledFeb (DayOfWeek firstIsOn, bool isLeap){ //etc } public void JustDataCoupledFeb(int year){ //Could calc day of week of the first, leap year } public void PrintJanuary(DayOfWeek beginsOn){ // etc } } class CalendarMaker{ public void PrintYear(int year){ CalendarPrint cp = new CalendarPrint(); DayOfWeek firstIsOn = CalcDayOfWeekForJan(year); cp.PrintJanuary(firstIsOn); firstIsOn += 3; bool isLeap = IsLeapYear(year); cp.ControlCoupledFeb(firstIsOn, isLeap); // etc } DayOfWeek CalcDayOfWeekForJan(int year){ // etc } bool IsLeapYear(int year){ // etc } 328 Thinking in C# www.MindView.net } Control coupling is one of the most common design mistakes in object-oriented code While it’s not inconceivable that you’d have one class for printing and another for doing the date calculations, it’s almost certainly a better design if both of these concerns were handled by a single class External, Common, and Content coupling If two classes communicate via a non-native object, such as via a file, they are externally coupled Challenges with external coupling include more failure modes (what if the file is deleted by an unknowing user?) and much harder debugging, because it is harder to trace the exact state transitions of the communication object Internet programming frameworks often rely on external coupling via cookies or URL rewriting Common coupling occurs when two classes are dependent on the same static data Obviously, any change to the static data will affect all the classes that rely on it When A and B directly modify each other’s internal state without going through properties, they are content coupled This is the most obvious form of coupling and most designers learn to avoid it very quickly Cohesion The never-to-be-reached goal with coupling is fully independent objects and methods Seemingly, the best way to achieve this is to place all your work within a single Main( ) method; you’ll still be dependent on the NET Framework Library methods, but you won’t have any dependencies between your own methods and objects because you won’t define any! Needless to say, something else is in play in good designs: cohesion Cohesion is the degree to which all the elements of a method or type are related to each other It is not quite the opposite of coupling, but it’s related In general, the more cohesive a class, the looser is its external coupling But there is a problem: cohesion tends to break a task into more and more sub-tasks, but coordinating those sub-tasks tends to increase coupling The fun in software design is attempting to discover a way to have your highly cohesive cake and loosely couple it, too A cohesive class is one that implements the intuitive set of behaviors implied by its name – a cohesive BaseballStatistics class would contain not just the Chapter 9: Coupling and Cohesion 329 winning scores, but the myriad details that fans expect The questions that are asked to increase cohesion are the same that determine whether something should be turned into an object: “Does this make sense as something whose identity should be separate from other things?” and “Do these things intuitively belong together?” These questions, not “Does this correspond to something physical in the real world?” are what should guide your object-oriented designs Just as there are several types of coupling, so too are there several types of cohesion Functional cohesion A functionally cohesive method is one that is self-contained, whose every argument has import, and that modifies one thing int Add(int x, int y){ return x + y; } is an example of such a method Such methods are easy to read, test, and modify Sequential cohesion Many methods use the output of one step as inputs to another step When this happens, the methods are said to exhibit sequential cohesion (or sequential association) class AbstractExpressionist{ public void MakeCanvas(){ Paint p = ChooseRandomPaint(); SplatterPaintRandomly(p); } } The method AbstractExpressionist.SplatterPaintRandomly( ) is sequentially associated with AbstractExpressionist.ChooseRandomPaint( ): one cannot lay down a brushstroke until one has chosen a paint All object methods are sequentially associated with the constructor, while static methods are sequentially associated with the static constructor and class loading Try to avoid creating sequential associations between methods that are not private While it’s not a great sin to require some amount of sequencing from the client programmer, strive for functional cohesion in the design of the non-private methods in your classes Consider this poor design: 330 Thinking in C# www.ThinkingIn.NET //:c09:CarAndDriver1.cs //Poor design class Driver{ Car c = new Car(); public void Start(){ c.PutInNeutral(); c.InsertKey(); c.TurnKey(); } } class Car{ public void PutInNeutral(){ … } public void InsertKey(){ … } public void TurnKey(){ … } //…etc… }///:~ Here, the beginning designer might think that Driver.Start( ) is just calling the sequentially associated Car.PutInNeutral( ), Car.InsertKey( ), and Car.TurnKey( ) methods and that because the starting sequence does not directly manipulate any of the Car instance data, the Start( ) method may as well be in the Driver class Wrong Put aside the appeal to intuition that Car is the “obvious” place to put the Start( ) method and objectively look at the choice to put Start( ) in Driver One thing that jumps out immediately is that to test the Driver.Start( ) design, you would need to create a Driver object and a Car object Whereas, if you had: class Car{ public void PutInNeutral(){ … } public void InsertKey(){ … } public void TurnKey(){ … } public void Start(){ PutInNeutral(); InsertKey(); TurnKey(); } //…etc… } Chapter 9: Coupling and Cohesion 331 you would only need to create a Car object and Start( ) would call the methods on this (and raise the question: should only Start( ) should be public?) One of the key insights of the Extreme Programming movement is that if something is hard to test, there’s probably something wrong with the design Conversely, the easier something is to test, the better the design is likely to be (plus, it’s easier) Further, think about modifying the Driver.Start( ) design to allow for the logic that only stick-shift cars need be put in neutral before starting: //:c09:CarAndDriver2.cs //Consequence of following the previous poor design class Driver { void Start(Car c){ if (c is StickShift) { StickStart((StickShift) c); } else { QuickStart(c); } } void StickStart(StickShift c){ c.PutInNeutral(); QuickStart(c); } void QuickStart(Car c){ c.InsertKey(); c.TurnKey(); } // etc } This is typical of the refactoring that a programmer with a procedural background would be likely to produce early in their object-oriented days A person with more object-oriented experience would likely something like the following, which uses a virtual method call4: //:c09:CarAndDriver3.cs abstract class Car { Putting aside the question of whether introducing new classes such as Key and Transmission might not be the best way to handle the issue 332 Thinking in C# www.MindView.net abstract public void Start(); protected void QuickStart(){ InsertKey(); TurnKey(); } public void InsertKey(){ //…etc… } public void TurnKey() { //…etc… } } class StickShift : Car { public override void Start(){ PutInNeutral(); QuickStart(); } void PutInNeutral(){ //…etc… } } class Automatic : Car { public override void Start(){ QuickStart(); } } class Driver { Car c; void Start(){ c.Start(); } // etc }///:~ In this example, the transmission logic is associated with the sub-type of the Car, not the Driver This means that a Driver can start any kind of Car, and new types of transmission could be added to the mix by subtyping Car without touching existing code Chapter 9: Coupling and Cohesion 333 Communicational cohesion Communicational association occurs when a group of methods acts on the same, single piece of data This is the part-and-parcel of object-oriented programming: instance methods work on the data associated with the this reference, while static methods work on the unique class data Consider the String class, for instance, and all of its methods for converting, trimming, searching, and concatenating: all sorts of behavior are available to work on the particular string referenced by the particular variable Communicational association and sequential association are different in that sequential association is an assembly line, while the order in which communicationally associated methods are called is unimportant Of course, the this object is only a stepping-stone to individual instance fields; if you had, for instance: //:c09:Boombox.cs class Boombox{ float fmRadioFrequency; void TuneInRadio(float frequency){ fmRadioFrequency = frequency; } int currentCdTrack; void SetCdTrack(int track){ currentCdTrack = track; } }///:~ it would be incorrect to say that Boombox.TuneInRadio( ) and Boombox.SetCdTrack( ) exhibited communicational cohesion (Note also that these methods would be better implemented as properties, which would also not be considered communicationally cohesive.) Although the general rule of object-oriented programming is to place both the data and all the behavior associated with manipulating that data inside a single class (in our example, placing the Start( ) method within the Car class rather than the Driver class), there are times when you are trying to make a new, independent identity for the logic that is separate from the identity of the data 334 Thinking in C# www.ThinkingIn.NET This will be discussed in more detail later in this chapter, when we discuss multitiered architectures Procedural cohesion The original Driver.Start( ) method that called a series of methods on the Car c was marginal, but things are pretty clearly awry by the time methods are using procedural association, in which a method embodies sequential logic on more than one object that is not this //:c09:BadDriver.cs class Driver { Car c; Cellphone p; void Start(){ c.PutInNeutral(); c.InsertKey(); c.TurnKey(); p.PlugIn(); p.HandsFree = true; if (DestinationHome()) { p.Dial(p.SpeedDial.Home); // etc } } }///:~ Procedurally associated methods are hard to test and therefore hard to modify This version of Driver.Start( ) is sequentially controlling method calls in both the Car and the Cellphone; to test this method, you’d have to fully exercise all variations on the Car starting procedure and on the Cellphone initialization Clearly, you’d want to move the logic for these responsibilities into the Car and Cellphone classes Temporal cohesion Temporal association occurs when a method’s behavior is related to the time at which it is called The most common example of this might be a threaded program that “wakes up” periodically and performs some series of tasks related only by the fact they’re done at a particular time (check for new email, defragment the hard drive, send the day’s credit card transactions to the bank, etc.) Batch-mode processing, where temporal association is most likely to crop Chapter 9: Coupling and Cohesion 335 up, is fairly rare now that we have ample computing resources at hand Additionally, in C# individual threads are relatively cheap and easy to use; there are no obvious temptations that would lead to temporal association Threading will be covered in detail in chapter 16 Logical cohesion Logical association occurs when a method’s logic is determined by an external method that passes in a control value //:c09:LogicalAssociation.cs using System; class Reservation{ static ConfirmationNumber ConfirmReservation (string name, DateTime date, bool createIfNeeded){ Reservation res = null; if (date == DateTime.Today) { res = CheckTodaysArrivals(name); } else { res = CheckFutureArrivals(name, date); } if (res != null) { ConfirmationNumber cn = res.Confirmation; return cn; } else { if (createIfNeeded) { Reservation newRes = new Reservation(); // build reservation ConfirmationNumber cn = newRes.Confirmation; return cn; } else { return null; } } } // etc }///:~ The first step in Reservation.ConfirmReservation( ) is to see if the passedin Date is today; the result of this check activates different control paths, CheckTodaysArrivals( ) or CheckFutureArrivals( ) While the behavior of Reservation.ConfirmReservation( ) changes depending on the value of 336 Thinking in C# www.MindView.net Date, this is not logical cohesion because the test and the control is within Reservation.ConfirmReservation( ) However, the bool createIfNeeded argument is ugly It makes unit-testing Reservation.ConfirmReservation( ) literally twice as hard Logical association can almost always be transformed into at least procedural association This first example shows code that might call Reservation.ConfirmReservation( ) as it’s written: class TravelAgent{ void ConfirmPackage(Person customer){ bool create = customer.CreateResIfNull(); foreach(DateTime d in customer.TravelDates){ ConfirmationNumber cn = Reservation.ConfirmReservation (customer.Name, d, create); if (cn != null) { customer.AddConfirmation(cn); } } } } But a first step towards improving the code would be to refactor from logical to procedural association: //:c09:RefactoredReservations.cs using System; class Reservation { internal Reservation(string name, DateTime d){ // etc } public static ConfirmationNumber ConfirmReservation (string name, DateTime date){ // etc return cn; } ConfirmationNumber cn; public ConfirmationNumber Confirmation{ get { return cn; } Chapter 9: Coupling and Cohesion 337 set { cn = value; } } } class TravelAgent { public void ConfirmPackage(Person customer){ bool create = customer.CreateResIfNull(); foreach(DateTime d in customer.TravelDates){ string name = customer.Name; ConfirmationNumber cn = Reservation.ConfirmReservation(name, d); if (cn == null && create == true) { Reservation res = new Reservation(name, d); cn = res.Confirmation; } if (cn != null) { customer.AddConfirmation(cn); } } } } While this new version of TravelAgent.ConfirmPackage( ) may still not be ideal, where bool create is declared, assigned, and how it is used to affect behavior is all localized within TravelAgent.ConfirmPackage( ) You can see exactly where it comes from and what it affects With the original, logically associated versions of these methods, this was far less apparent Coincidental cohesion Coincidental association isn’t very common; it occurs when a method does two totally unrelated tasks: void PayTaxesAndPickUpALoafOfBread(){ PayTaxes(); PickUpALoafOfBread(); } The one place where coincidental cohesion is seen fairly commonly is with programmers who over-use graphical forms as a stand-in for object-oriented design This will be discussed in great detail in Chapter 14’s discussion of userinterface architectures 338 Thinking in C# www.ThinkingIn.NET Design is as design does The end-user has no interest in your software’s design They care that your software helps them their job easier They care that your software doesn’t irritate them They care that when they talk to you about what they need from the software, you listen and respond sympathetically (and in English, not Geek) They care that your software is cheap and reliable and robust Other than that, they don’t care if it’s written in assembly language, LISP, C#, or FORTRAN, and they sure as shooting don’t care about your class and sequence diagrams So there’s really nothing that matters about design except how easy it is to change or extend – the things that you have to when you discover that your existing code falls short in some manner So how you design for change? First, no harm It’s acceptable for you to decide not to make a change It’s acceptable for your change to turn out to be less helpful to the user than the original It’s acceptable for your change to be dead wrong, either because of miscommunication or because your fingers slipped on the keys and typed a plus instead of a minus What is unacceptable is for you to commit a change that breaks something else in your code And it is unacceptable for you to not know if your change breaks something else in the code Yet such a pathological state, where the only thing that developers can say about their code is a pathetic “Well, it shouldn’t cause anything else to change,” is very common Almost as common are situations where changes do, in fact, seem to randomly affect the behavior of the system as a whole It is counterintuitive, but the number one thing you can to speed up your development schedule is to write a lot of tests It doesn’t pay off in the first days, but it pays off in just a few weeks By the time you get several months into a project, you will be madly in love with your testing suite Once you develop a system with a proper suite of tests, you will consider it incompetent foolishness to develop without one With modern languages that support reflection, it has become possible to write test infrastructure systems that discover at runtime what methods are tests (by convention, methods that begin with the string “test”) and running them and reporting the results The predominant such system is JUnit, created by Kent Beck and Erich Gamma Several ports and refactorings of JUnit for NET Chapter 9: Coupling and Cohesion 339 languages are available, the most popular of which is Philip Craig’s NUnit Appendix C in this book goes into some detail on the use of NUnit Software systems are among the most complex structures built by humans, and unintended consequences are an inevitable part of manipulating those systems The speed with which you can change a software system is dependent on many things, but on nothing so much as your ability to isolate a change in both time and program structure Extensive unit tests, runnable by a batch process, is by far the best way to this Write boring code A good application is interesting; good code is boring The most boring code has no cyclomatic complexity, no coupling, and functional cohesion What can you say about: ///Adds two integers /// ///Thrown if x+y > Int32.MaxValue or < Int32.MinValue /// public int Add(int x, int y){ return x + y; } that goes beyond the code? Even if this were a core method called a million times per second, you could entrust to a junior programmer the task of modifying it to work with 64-bit long values At the other extreme is “clever code.” Clever code is characterized by high cyclomatic complexity and extensive coupling Often, clever code is written in an ill-advised attempt to increase performance Often, clever code runs slower than boring code Make names meaningful Type, method, and variable names should all be as descriptive as possible This is an area where the book’s examples are not a good guide; the 54 character columns of the book dictate that fully descriptive, fully spelled-out names can’t be used Needless to say, names must also be accurate This is an obvious quality feature and yet people skimp on it, as it requires a search-and-replace that may have to span multiple files Such an operation is no big deal with any kind of decent programming editor, but occasionally “clever code” interferes For instance, one 340 Thinking in C# www.MindView.net of us (Larry) was once stymied to see a testing suite break after a class was renamed during refactoring; it turned out that during initialization, the names of classes that implemented a filtering interface were read from a configuration file and dynamic class loading used to instantiate the desired filters; the configuration file contained the old name This is another lesson in the importance of unit-testing: usually one expects that a clean compile is all that is necessary to confirm a renaming operation, but a testing suite will flush out unexpected problems such as this in a matter of minutes, not the hours or days that might be required if the “obviously working” change had been checked in to source-code control Classes should be named with the noun of the domain concept that is being encapsulated: Car, Profit, or EducationalObjective You should not repeat the base-class name in descendant classes: Compact and FourDoor are good names for classes descending from Car, not CompactCar and FourDoorCar An alternative can be used sparingly: sometimes a class does not correspond to a domain concept but is created strictly to play a role in a design pattern In this situation, the class may be named according to the design role: ConcreteFlyweight or CompositeElement are acceptable names for classes that are playing particular roles in the Flyweight and Composite design patterns Better, even in this situation you should try to tie the role and the domain together: perhaps ConcreteGlyphFlyweight or CompositeGraphicElement Methods that return void should be named with a strong verb: Document.Print( ) If a method returns a value, the method name should be a noun-based description of the return value: Invoice.SalesTax( ) If a method is difficult to describe with strong words, there’s probably a problem with its cohesion If you can’t come up with something better than Execute( ) or DoCalculation( ), or if the method name includes conjunctions (Invoice.CalcFinalCostAndSendShippingOrder( )), it almost certainly has procedural cohesion or worse Sometimes, you’ll find that your class is like some standard library class Even if after consideration you decide that your class should not descend from the library class, you should strongly consider naming (and implementing!) its methods to correspond to those in the library class Limit complexity The number of lines in a method is not a good indicator that you should split it into two, but the cyclomatic complexity of the method is If you use Visual Studio Chapter 9: Coupling and Cohesion 341 ... correctly using the same code Or to put it The Ninja Project, Moreira et al., Communications of the ACM 44 (10), Oct 2001 For details, see http://www.ThinkingIn.Net 266 Thinking in C# www.ThinkingIn.NET... sensible way to things, and any other design indicates muddled thinking and is by definition broken This too is a trap As soon as you start thinking this way, 296 Thinking in C# www.MindView.net you’ll... { internal Bread() { Console.WriteLine("Bread()");} } public class Cheese { internal Cheese() { Console.WriteLine("Cheese()");} } 290 Thinking in C# www.ThinkingIn.NET public class Lettuce { internal