[ Team LiB ] |
8.1 Implementing an InterfaceThe syntax for defining an interface is as follows: [attributes] [access-modifier] interface interface-name [:base-list] {interface-body} Don't worry about attributes for now; they're covered in Chapter 18. Access modifiers, including public, private, protected, internal, and protected internal, are discussed in Chapter 4. The interface keyword is followed by the name of the interface. It is common (but not required) to begin the name of your interface with a capital I (thus, IStorable, ICloneable, IClaudius, etc.). The base-list lists the interfaces that this interface extends (as described in Section 8.1.1, later in this chapter). The interface-body is the implementation of the interface, as described next. Suppose you wish to create an interface that describes the methods and properties a class needs to be stored to and retrieved from a database or other storage such as a file. You decide to call this interface IStorable. In this interface you might specify two methods: Read( ) and Write( ), which appear in the interface-body: interface IStorable { void Read( ); void Write(object); } The purpose of an interface is to define the capabilities that you want to have available in a class. For example, you might create a class, Document. It turns out that Document types can be stored in a database, so you decide to have Document implement the IStorable interface. To do so, use the same syntax as if the new Document class were inheriting from IStorablea colon (:), followed by the interface name: public class Document : IStorable { public void Read( ) {...} public void Write(object obj) {...} // ... } It is now your responsibility, as the author of the Document class, to provide a meaningful implementation of the IStorable methods. Having designated Document as implementing IStorable, you must implement all the IStorable methods, or you will generate an error when you compile. This is illustrated in Example 8-1, in which the Document class implements the IStorable interface. Example 8-1. Using a simple interfaceusing System; // declare the interface interface IStorable { // no access modifiers, methods are public // no implementation void Read( ); void Write(object obj); int Status { get; set; } } // create a class which implements the IStorable interface public class Document : IStorable { // store the value for the property private int status = 0; public Document(string s) { Console.WriteLine("Creating document with: {0}", s); } // implement the Read method public void Read( ) { Console.WriteLine( "Implementing the Read Method for IStorable"); } // implement the Write method public void Write(object o) { Console.WriteLine( "Implementing the Write Method for IStorable"); } // implement the property public int Status { get { return status; } set { status = value; } } } // Take our interface out for a spin public class Tester { static void Main( ) { // access the methods in the Document object Document doc = new Document("Test Document"); doc.Status = -1; doc.Read( ); Console.WriteLine("Document Status: {0}", doc.Status); } } Output: Creating document with: Test Document Implementing the Read Method for IStorable Document Status: -1 Example 8-1 defines a simple interface, IStorable, with two methods(Read( ) and Write( )) and a property (Status) of type integer. Notice that the property declaration does not provide an implementation for get( ) and set( ), but simply designates that there is a get( ) and a set( ): int Status { get; set; } Notice also that the IStorable method declarations do not include access modifiers (e.g., public, protected, internal, private). In fact, providing an access modifier generates a compile error. Interface methods are implicitly public because an interface is a contract meant to be used by other classes. You cannot create an instance of an interface; instead you instantiate a class that implements the interface. The class implementing the interface must fulfill the contract exactly and completely. Document must provide both a Read( ) and a Write( ) method and the Status property. How it fulfills these requirements, however, is entirely up to the Document class. Although IStorable dictates that Document must have a Status property, it does not know or care whether Document stores the actual status as a member variable or looks it up in a database. The details are up to the implementing class. 8.1.1 Implementing More Than One InterfaceClasses can implement more than one interface. For example, if your Document class can be stored and it also can be compressed, you might choose to implement both the IStorable and ICompressible interfaces. To do so, change the declaration (in the base-list) to indicate that both interfaces are implemented, separating the two interfaces with commas: public class Document : IStorable, ICompressible Having done this, the Document class must also implement the methods specified by the ICompressible interface (which is declared in Example 8-2): public void Compress( ) { Console.WriteLine("Implementing the Compress Method"); } public void Decompress( ) { Console.WriteLine("Implementing the Decompress Method"); } 8.1.2 Extending InterfacesIt is possible to extend an existing interface to add new methods or members, or to modify how existing members work. For example, you might extend ICompressible with a new interface, ILoggedCompressible, which extends the original interface with methods to keep track of the bytes saved: interface ILoggedCompressible : ICompressible { void LogSavedBytes( ); } Classes are now free to implement either ICompressible or ILoggedCompressible, depending on whether they need the additional functionality. If a class does implement ILoggedCompressible, it must implement all the methods of both ILoggedCompressible and ICompressible. Objects of that type can be cast either to ILoggedCompressible or to ICompressible. 8.1.3 Combining InterfacesSimilarly, you can create new interfaces by combining existing interfaces and, optionally, adding new methods or properties. For example, you might decide to create IStorableCompressible. This interface would combine the methods of each of the other two interfaces, but would also add a new method to store the original size of the precompressed item: interface IStorableCompressible : IStoreable, ILoggedCompressible { void LogOriginalSize( ); } Example 8-2 illustrates extending and combining interfaces. Example 8-2. Extending and combining interfacesusing System; interface IStorable { void Read( ); void Write(object obj); int Status { get; set; } } // here's the new interface interface ICompressible { void Compress( ); void Decompress( ); } // Extend the interface interface ILoggedCompressible : ICompressible { void LogSavedBytes( ); } // Combine Interfaces interface IStorableCompressible : IStorable, ILoggedCompressible { void LogOriginalSize( ); } // yet another interface interface IEncryptable { void Encrypt( ); void Decrypt( ); } public class Document : IStorableCompressible, IEncryptable { // hold the data for IStorable's Status property private int status = 0; // the document constructor public Document(string s) { Console.WriteLine("Creating document with: {0}", s); } // implement IStorable public void Read( ) { Console.WriteLine( "Implementing the Read Method for IStorable"); } public void Write(object o) { Console.WriteLine( "Implementing the Write Method for IStorable"); } public int Status { get { return status; } set { status = value; } } // implement ICompressible public void Compress( ) { Console.WriteLine("Implementing Compress"); } public void Decompress( ) { Console.WriteLine("Implementing Decompress"); } // implement ILoggedCompressible public void LogSavedBytes( ) { Console.WriteLine("Implementing LogSavedBytes"); } // implement IStorableCompressible public void LogOriginalSize( ) { Console.WriteLine("Implementing LogOriginalSize"); } // implement IEncryptable public void Encrypt( ) { Console.WriteLine("Implementing Encrypt"); } public void Decrypt( ) { Console.WriteLine("Implementing Decrypt"); } } public class Tester { static void Main( ) { // create a document object Document doc = new Document("Test Document"); // cast the document to the various interfaces IStorable isDoc = doc as IStorable; if (isDoc != null) { isDoc.Read( ); } else Console.WriteLine("IStorable not supported"); ICompressible icDoc = doc as ICompressible; if (icDoc != null) { icDoc.Compress( ); } else Console.WriteLine("Compressible not supported"); ILoggedCompressible ilcDoc = doc as ILoggedCompressible; if (ilcDoc != null) { ilcDoc.LogSavedBytes( ); ilcDoc.Compress( ); // ilcDoc.Read( ); } else Console.WriteLine("LoggedCompressible not supported"); IStorableCompressible isc = doc as IStorableCompressible; if (isc != null) { isc.LogOriginalSize( ); // IStorableCompressible isc.LogSavedBytes( ); // ILoggedCompressible isc.Compress( ); // ICompressible isc.Read( ); // IStorable } else { Console.WriteLine("StorableCompressible not supported"); } IEncryptable ie = doc as IEncryptable; if (ie != null) { ie.Encrypt( ); } else Console.WriteLine("Encryptable not supported"); } } Output: Creating document with: Test Document Implementing the Read Method for IStorable Implementing Compress Implementing LogSavedBytes Implementing Compress Implementing LogOriginalSize Implementing LogSavedBytes Implementing Compress Implementing the Read Method for IStorable Implementing Encrypt Example 8-2 starts by implementing the IStorable interface and the ICompressible interface. The latter is extended to ILoggedCompressible and then the two are combined into IStorableCompressible. Finally, the example adds a new interface, IEncryptable. The Tester program creates a new Document object and then casts it to the various interfaces. When the object is cast to ILoggedCompressible, you can use the interface to call methods on Icompressible because ILoggedCompressible extends (and thus subsumes) the methods from the base interface: ILoggedCompressible ilcDoc = doc as ILoggedCompressible; if (ilcDoc != null) { ilcDoc.LogSavedBytes( ); ilcDoc.Compress( ); // ilcDoc.Read( ); } You cannot call Read( ), however, because that is a method of IStorable, an unrelated interface. And if you uncomment out the call to Read( ), you will receive a compiler error. If you cast to IStorableCompressible (which combines the extended interface with the Storable interface), you can then call methods of IStorableCompressible, Icompressible, and IStorable: IStorableCompressible isc = doc as IStorableCompressible if (isc != null) { isc.LogOriginalSize( ); // IStorableCompressible isc.LogSavedBytes( ); // ILoggedCompressible isc.Compress( ); // ICompressible isc.Read( ); // IStorable } |
[ Team LiB ] |