1. Trang chủ
  2. » Công Nghệ Thông Tin

Programming C# 4.0 phần 3 ppt

85 295 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 85
Dung lượng 10,34 MB

Nội dung

Summary So far, we’ve seen how to create classes; to model relationships between instances of those classes through association, composition, and aggregation; and to create rela- tionships between classes by derivation. We also saw how virtual functions enable derived classes to replace selected aspects of a base class. We saw how to use protected and protected internal to control the visibility of mem- bers to derived classes. Then, we saw how we can use either abstract classes and methods or interfaces to define public contracts for a class. Finally, we looked at a means of examining the inheritance hierarchy by hand, and verifying whether an object we are referencing through a base class is, in fact, an instance of a more derived class. In the next chapter, we are going to look at some other techniques for code reuse and extensibility that don’t rely on inheritance. 142 | Chapter 4: Extensibility and Polymorphism CHAPTER 5 Composability and Extensibility with Delegates In the preceding two chapters, we saw how to encapsulate behavior and information with classes. Using the concepts of association, composition, aggregation, and deriva- tion, we modeled relationships between those classes and looked at some of the benefits of polymorphism along with the use and abuse of virtual functions and their implied contracts with derived classes. In this chapter, we’ll look at a functional (rather than class-based) approach to com- position and extensibility, and see how we can use this to implement some of the pat- terns that have previously required us to burn our one and only base class and override virtual functions; and all with the added benefit of a looser coupling between classes. Let’s start with another example. This time, we want to build a system that processes incoming (electronic) documents prior to publication. We might want to do an auto- mated spellcheck, repaginate, perform a machine translation for a foreign-language website, or perform one of any other number of operations that our editors will devise during the development process and beyond. After some business analysis, our platform team has given us a class called Document, which is shown in Example 5-1. This is their baby, and we’re not allowed to mess with it. Example 5-1. The Document class public sealed class Document { // Get/set document text public string Text { get; set; } // Date of the document 143 public DateTime DocumentDate { get; set; } public string Author { get; set; } } It has simple properties for its Text, the DocumentDate, and the Author, and no other methods. What Is Coupling? Two classes are said to be coupled if a change to one requires a change to another. We saw examples of that in the previous chapter. When we created our NamedPerson class, it required changes to the FirefighterBase and the Administrator classes. We therefore say that FirefighterBase and Administrator are coupled to NamedPerson. Of course, any class or function that refers to another class or function is coupled to that class—that’s unavoidable (indeed, desirable). But to make testing simpler and systems more reliable, we try to ensure that we minimize the number of other types to which any class or function is coupled, and that we minimize the number of couplings between any two types. That way, any given change to a class will have a minimal number of knock-on effects elsewhere in the system. We also try to ensure that we organize classes into conceptual groupings called layers so that more tightly coupled classes live together in one layer, and that there are a minimal number of well-controlled couplings between layers. As part of that layered approach, it is usual to try to ensure that most couplings go one-way; classes of a “lower” layer should not depend on classes in a layer above. That way, we can further limit (and understand) the way changes propagate through the system. The layers act like firewalls, blocking the further impact of a change. As usual with software design, these disciplines are not hard-and-fast rules, and they are not imposed by the platform or language; but they are common practices that the platform and language are designed to support. Now we want to be able to process the document. At the very least, we want to be able to Spellcheck, Repaginate, or Translate it (into French, say). Because we can’t change the Document class, we’ll implement these methods in a static utility class of common processes, as we learned in Chapter 3. Example 5-2 shows this class, although the implementations are obviously just placeholders—we’re illustrating how to structure the code here, and trying to write a real spellchecker would be a rather large distraction. 144 | Chapter 5: Composability and Extensibility with Delegates Example 5-2. Some document processing methods static class DocumentProcesses { public static void Spellcheck( Document doc ) { Console.WriteLine("Spellchecked document."); } public static void Repaginate( Document doc) { Console.WriteLine("Repaginated document."); } public static void TranslateIntoFrench( Document doc ) { Console.WriteLine("Document traduit."); } // } Now we can build a simple example of a document processor that translates, spell- checks, and then repaginates the document (see Example 5-3). Example 5-3. Processing a document static class DocumentProcessor { public static void Process(Document doc) { DocumentProcesses.TranslateIntoFrench(doc); DocumentProcesses.Spellcheck(doc); DocumentProcesses.Repaginate(doc); } } And we can call on it from our main function, to process a couple of documents, as shown in Example 5-4. Example 5-4. A program to test the document processing classes class Program { static void Main(string[] args) { Document doc1 = new Document { Author = "Matthew Adams", DocumentDate = new DateTime(2000, 01, 01), Text = "Am I a year early?" }; Document doc2 = new Document { Author = "Ian Griffiths", Composability and Extensibility with Delegates | 145 DocumentDate = new DateTime(2001, 01, 01), Text = "This is the new millennium, I promise you." }; Console.WriteLine("Processing document 1"); DocumentProcessor.Process(doc1); Console.WriteLine(); Console.WriteLine("Processing document 2"); DocumentProcessor.Process(doc2); Console.ReadKey(); } } Compile and run that, and you’ll see the following output: Processing document 1 Document traduit. Spellchecked document. Repaginated document. Processing document 2 Document traduit. Spellchecked document. Repaginated document. We encapsulated a particular set of processing instructions, executed in a particular order, in this (static) DocumentProcessor class so that we can easily reuse it with dif- ferent client applications that want a standard, reliable means of performing our “trans- late into French” process. So far, this should all be pretty familiar. But what about a different set of processing operations, one that leaves the document in its native language and just spellchecks and repaginates? We could just create a second DocumentProcessor-like class, and encapsulate the rele- vant method calls in a process function: static class DocumentProcessorStandard { public static void Process(Document doc) { DocumentProcesses.Spellcheck(doc); DocumentProcesses.Repaginate(doc); } } And then we could add some calls to that processor in our Main method: Console.WriteLine(); Console.WriteLine("Processing document 1 (standard)"); DocumentProcessorStandard.Process(doc1); Console.WriteLine(); Console.WriteLine("Processing document 2 (standard)"); DocumentProcessorStandard.Process(doc2); 146 | Chapter 5: Composability and Extensibility with Delegates Nothing is intrinsically wrong with any of this; it clearly works, and we have a nice enough design that neatly encapsulates our processing. We note that each DocumentProcessor is coupled to the Document class, and also to each method that it calls on the DocumentProcesses class. Our client is coupled to the Document and each DocumentProcessor class that it uses. If we go back to the specification we showed earlier, we see that we are likely to be creating a lot of different functions to modify the document as part of the production process; they’ll slip in and out of use depending on the type of document, other systems we might have to work with, and the business process of the day. Rather than hardcoding this process in an ever-increasing number of processor classes (and coupling those to an ever-increasing number of DocumentProcesses), it would ob- viously be better if we could devolve this to the developers on our production team. They could provide an ordered set of processes (of some kind) to the one and only DocumentProcessor class that actually runs those processes. We can then focus on making the process-execution engine as efficient and reliable as possible, and the production team will be able to create sequences of processes (built by either us, them, contractors, or whoever), without having to come back to us for updates all the time. Figure 5-1 represents that requirement as a diagram. Figure 5-1. Document processor architecture The document is submitted to the document processor, which runs it through an or- dered sequence of processes. The same document comes out at the other end. Composability and Extensibility with Delegates | 147 OK, let’s build a DocumentProcessor class that implements that (see Example 5-5). Example 5-5. An adaptable document processor class DocumentProcessor { private readonly List<DocumentProcess> processes = new List<DocumentProcess>(); public List<DocumentProcess> Processes { get { return processes; } } public void Process(Document doc) { foreach(DocumentProcess process in Processes) { process.Process(doc); } } } Our document processor has a List of DocumentProcess objects (a hypothetical type we’ve not written yet). A List<T> is an ordered collection—that is to say that the item you Add at index 0 stays at index 0, and is first out of the block when you iterate the list, and so on. That means our Process method can just iterate over the collection of DocumentProcess objects, and call some equally hypothetical Process method on each to do the processing. But what type of thing is a DocumentProcess? Well, we already saw a solution we can use—we could create a DocumentProcess abstract base, with a Process abstract method: abstract class DocumentProcess { public abstract void Process(Document doc); } We then need to create a derived class for every processing operation, as shown in Example 5-6. Example 5-6. Implementations of the abstract DocumentProcess class SpellcheckProcess : DocumentProcess { public override void Process(Document doc) { DocumentProcesses.Spellcheck(doc); } } 148 | Chapter 5: Composability and Extensibility with Delegates class RepaginateProcess : DocumentProcess { public override void Process(Document doc) { DocumentProcesses.Repaginate(doc); } } class TranslateIntoFrenchProcess : DocumentProcess { public override void Process(Document doc) { DocumentProcesses.TranslateIntoFrench(doc); } } Now we can configure a processor in our client by adding some process objects to the list (see Example 5-7). Example 5-7. Configuring a document processor with processes static DocumentProcessor Configure() { DocumentProcessor rc = new DocumentProcessor(); rc.Processes.Add(new TranslateIntoFrenchProcess()); rc.Processes.Add(new SpellcheckProcess()); rc.Processes.Add(new RepaginateProcess()); return rc; } See how we are adding the processes to the processor in the same order we had in our function calls previously? Our process objects are logically similar to function calls, and the order in which they appear is logically similar to a program, except that they are composed at runtime rather than compile time. We can then use this configuration method in our client, and call on the processor to process our documents, as shown in Example 5-8. Example 5-8. Using the dynamically configured processor static void Main(string[] args) { Document doc1 = new Document { Author = "Matthew Adams", DocumentDate = new DateTime(2000, 01, 01), Text = "Am I a year early?" }; Document doc2 = new Document { Author = "Ian Griffiths", DocumentDate = new DateTime(2001, 01, 01), Text = "This is the new millennium, I promise you." }; Composability and Extensibility with Delegates | 149 DocumentProcessor processor = Configure(); Console.WriteLine("Processing document 1"); processor.Process(doc1); Console.WriteLine(); Console.WriteLine("Processing document 2"); processor.Process(doc2); Console.ReadKey(); } If you compile and run, you’ll see the same output as before: Processing document 1 Document traduit. Spellchecked document. Repaginated document. Processing document 2 Document traduit. Spellchecked document. Repaginated document. This is a very common pattern in object-oriented design—encapsulating a method in an object and/or a process in a sequence of objects. What’s nice about it is that our DocumentProcessor is now coupled only to the Document class, plus the abstract base it uses as a contract for the individual processes. It is no longer coupled to each and every one of those processes; they can vary without requiring any changes to the processor itself, because they implement the contract demanded by the abstract base class. Finally, the processing sequence (the “program” for the DocumentProcessor) is now the responsibility of the client app, not the processor library; so our different production teams can develop their own particular sequences (and, indeed, new processes) without having to refer back to the core team and change the document processor in any way. In fact, the only thing that is a bit of a pain about this whole approach is that we have to declare a new class every time we want to wrap up a simple method call. Wouldn’t it be easier just to be able to refer to the method call directly? C# provides us with a tool to do just that: the delegate. Functional Composition with delegate We just wrote some code that wraps up a method call inside an object. The call itself is wrapped up in another method with a well-known signature. You can think of a delegate as solving that same sort of problem: it is an object that lets us wrap up a method call on another object (or class). 150 | Chapter 5: Composability and Extensibility with Delegates But while our DocumentProcess classes have their methods hardcoded into virtual func- tion overrides, a delegate allows us to reference a specific function (from a given class or object instance) at runtime, then use the delegate to execute that function. So, in the same way that a variable can be considered to contain a reference to an object, a delegate can be thought to contain a reference to a function (see Figure 5-2). Figure 5-2. Delegates and variables Before we get into the specific C# syntax, I just want to show you that there isn’t anything mystical about a delegate; in fact, there is a class in the .NET Framework called Delegate which encapsulates the behavior for us. As you might expect, it uses properties to store the reference to the function. There are two, in fact: Method (which indicates which member function to use) and Target (which tells us the object on which the method should be executed, if any). As you can see, the whole thing is not totally dissimilar in concept from our previous DocumentProcess base class, but we don’t need to derive from Delegate to supply the function to call. That ability has moved into a property instead. That’s all there is to a delegate, really. Functional Composition with delegate | 151 [...]... first (they are supposed to be quick), and look at their return values If any fail, we can give up (see Figure 5 -3) We could implement that by rewriting our DocumentProcessor as shown in Example 5- 13 Generic Actions with Action | 157 Figure 5 -3 Document processor with checking Example 5- 13 Adding quick checking to the document processor class DocumentProcessor { class ActionCheckPair { public Action... compact than a static class/function declaration, we can get more compact and expressive still, using lambda expression syntax, which was added in C# 3. 0 (anonymous methods having been around since C# 2.0) Creating Delegates with Lambda Expressions In the 1 930 s (a fertile time for computing theory!) two mathematicians named Church and Kleene devised a formal system for investigating the properties of... Document traduit (Document 2) Spellchecked document (Document 2) Repaginated document (Document 2) Highlighting 'millennium' (Document 2) Document traduit (Document 3) Spellchecked document (Document 3) Repaginated document (Document 3) (Document 3) Executed 9 processes OK, our production team is very happy with all of that, but they have another requirement Apparently, they have one team working on some... has seen that processing is complete Tool2 has seen that processing is complete Tool1 has seen processing Tool2 has seen processing Document traduit (Document 3) Spellchecked document (Document 3) Repaginated document (Document 3) (Document 3) Too11 has seen that processing is complete Tool2 has seen that processing is complete Executed 9 processes You might notice that the event handlers have been... Expressions | 1 63 Some languages enforce the no side effects constraint; but in C# there is nothing to stop you from writing a lambda expression such as this one: (x,y,z) => { SomeStaticClass.CrashAndBurnAndMessWithEverything(); x.ModifyInternalState(); return x + y + z; } (Incidentally, this form of lambda expression, using braces to help define its body, is called a statement-form lambda.) In C#, a lambda... 1) Document traduit (Document 2) Spellchecked document (Document 2) Repaginated document (Document 2) Highlighting 'millennium' (Document 2) Document traduit (Document 3) Spellchecked document (Document 3) Repaginated document (Document 3) We took advantage of the fact that an anonymous method has access to variables declared in its parent scope, in addition to anything in its own scope For more information... up along the way, rather than producing errors at runtime if you get it wrong It is so compact, expressive, and powerful that you can probably get through your entire C# programming career without ever worrying about the classes the C# compiler emits which derive from that Delegate class and implement it all So, why have we just spent a page or so discussing these implementation details, if we’re never... modified by the function; we just map from the input parameters to a result Lambda expressions in C# use syntax very similar to this to define functional expressions Here’s the C# equivalent of that mathematical expression we used earlier: (x,y,z) => x + y + z; Notice how it rather cutely uses => as the programming language equivalent of → C++ users should not mistake this for the -> operator—it is... = "(Document 1)"; processor.Process(doc1); Console.WriteLine(); documentBeingProcessed = "(Document 2)"; processor.Process(doc2); Console.WriteLine(); documentBeingProcessed = "(Document 3) "; processor.Process(doc3); Console.WriteLine(); Console.WriteLine("Executed " + processCount + " processes."); } Console.ReadKey(); We added a processCount variable at method scope, which we initialized to zero... = "(Document 1)"; processor.Process(doc1); Console.WriteLine(); documentBeingProcessed = "(Document 2)"; processor.Process(doc2); Console.WriteLine(); documentBeingProcessed = "(Document 3) "; processor.Process(doc3); } Console.ReadKey(); We added a third document to the set, just so that we can see more get processed Then we set up a local variable called documentBeingProcessed As we move through the . new DateTime( 200 0, 01 , 01 ), Text = "Am I a year early?" }; Document doc2 = new Document { Author = "Ian Griffiths", DocumentDate = new DateTime( 200 1, 01 , 01 ), Text =. DateTime( 200 0, 01 , 01 ), Text = "Am I a year early?" }; Document doc2 = new Document { Author = "Ian Griffiths", Composability and Extensibility with Delegates | 145 DocumentDate. Griffiths", Composability and Extensibility with Delegates | 145 DocumentDate = new DateTime( 200 1, 01 , 01 ), Text = "This is the new millennium, I promise you." }; Console.WriteLine("Processing

Ngày đăng: 06/08/2014, 09:20

TỪ KHÓA LIÊN QUAN