Tải bản đầy đủ (.pdf) (24 trang)

Interfaces

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (590.47 KB, 24 trang )

235
■ ■ ■
CHAPTER 9
Interfaces
I
nterfaces may contain method declarations (including properties and events) and static
methods, but no instance method definitions and no instance data members. As you already
know, interfaces partially take the place of multiple inheritance in C++/CLI. However, the
words “inherit from” are not used; rather, the word used is “implements,” since the class that
implements an interface must provide the method bodies for all the instance methods declared in
the interface, even if it is an abstract class.

Note
Although the CLI itself does allow an abstract class implementing an interface to leave unimplemented
methods, this is not allowed in C++/CLI.
You’re probably used to using pointers or references to base classes in classic C++ to write
polymorphic functions. You can do this with handles to interfaces, too. Frequently, you write
code that uses interface handles if you want that code to be usable on a wide variety of possibly
unrelated objects (for example, a method that takes an interface handle as a parameter). As
long as all those object types implement the interface, you can use the function and never need
to know the actual underlying object type. And because each class that implements an inter-
face defines its own implementation of the interface methods, the behavior of different classes
implementing the same interface can be quite diverse.
Interfaces vs. Abstract Classes
Abstract classes and interfaces are somewhat similar in functionality but have different uses in
CLI programming. A class may implement many interfaces, but it can only be derived from one
class. In a well-designed class library, the relationship between a derived class and an abstract
base class is usually referred to as an “is-a” relationship. The interface relationship is slightly
different—a class that implements an interface has a relationship with the interface that might
be described as “as-a.” Accessing an object of type R through a specific interface I is equivalent
to treating the object of type R “as an” I. You could also say that implementing an interface is


like fulfilling a contract. Interfaces generally encapsulate some aspect of the behavior of an
object, not the identity of an object as an abstract base class does. A quick glance at the .NET
Framework interface shows that many have the “-ible” or “-able” suffix: IEnumerable,
Hogenson_705-2C09.fm Page 235 Thursday, October 19, 2006 8:01 AM
236
CHAPTER 9

INTERFACES
IComparable, IDisposable. Often, but not always, interfaces relate to specific activities that an
object is capable of participating in.
In practical terms, abstract classes are easier to change in later versions of a library. If you
ship a library with an interface and later release a new version of the library with an additional
method, you force everyone who uses that interface to add the method to their classes. With an
abstract class, you have a choice to provide a virtual implementation of the method, so as long
as that implementation is acceptable for any derived classes, users of the abstract class don’t
need to make any changes. With interfaces, there is no choice: any classes implementing the
modified interface will have to add the implementation. Depending on the situation, this
might or might not be desirable.
Declaring Interfaces
Listing 9-1 shows how an interface is declared and used in C++/CLI. The contextual keyword
interface is used with class. All members of an interface are automatically public, so no access
specifier is necessary in the interface declaration. Any other access control specifier is an error.
The interface is used rather like a base class, except that more than one interface may be specified
in the interface list. Methods that implement interface methods must be virtual.
Listing 9-1. Declaring and Implementing an Interface
// interface.cpp
interface class IInterface
{
void f();
int g();

};
ref class R : IInterface
{
public:
// The virtual keyword is required to implement the interface method.
virtual void f() { }
virtual int g() { return 1; }
};
If multiple interfaces are to be implemented, they are separated by commas on the base
list, as shown in Listing 9-2.
Listing 9-2. Implementing Multiple Interfaces
// interfaces_multiple.cpp
interface class IA { void f(); };
Hogenson_705-2C09.fm Page 236 Thursday, October 19, 2006 8:01 AM
CHAPTER 9

INTERFACES
237
interface class IB { void g(); };
// Implement multiple interfaces:
ref class R : IA, IB
{
public:
virtual void f() {}
virtual void g() {}
};
The base list after the colon following the class name lists all the interfaces to be implemented
and the base class (if specified) in no particular order. An implicit base class, Object, for reference
types, or System::ValueType, for value types, may be listed explicitly, as in Listing 9-3.
Listing 9-3. Explicitly Specifying Implicit Base Classes

// interface_list.cpp
using namespace System;
interface class IA {};
interface class IB {};
ref class Base : IA // OK
{ };
ref class Derived : Base, IA // OK : Base class first.
{ };
ref class A : Object, IA // OK: Object may be explicitly stated.
{ };
value class V : ValueType, IA // OK: Value class inherits from ValueType.
{ };
ref class B : IB, Base // OK. Base class need not appear first (as in C#).
{ };
Interfaces Implementing Other Interfaces
An interface declaration may itself call for the implementation of other interfaces. When this
construct is used, it means that the class implementing the interface must implement all the
methods declared in the interface body, as well as any methods declared in interfaces added to
the base class list. Listing 9-4 illustrates this pattern.
Hogenson_705-2C09.fm Page 237 Thursday, October 19, 2006 8:01 AM
238
CHAPTER 9

INTERFACES
Listing 9-4. Interface Inheritance
// interfaces_implementing_interfaces.cpp
interface class IA { void f(); };
interface class IB : IA { void g(); };
ref class R : IB
{

public:
virtual void f() {}
virtual void g() {}
};
When interfaces inherit from other interfaces, the new and override specifiers are not used
on the method declarations. These specifiers are only applicable to class inheritance. In fact,
an interesting case is the one of a class that inherits a method from a base class and also imple-
ments a method with the same name from an interface. In that case, new would indicate that
the method is different from the base class method (see Listing 9-5).
Listing 9-5. Using new to Implement an Interface Method
// base_and_interface.cpp
using namespace System;
ref class B
{
public:
virtual void f() { Console::WriteLine("B::f"); }
virtual void g() { Console::WriteLine("B::g"); }
};
interface class I
{
void f();
void g();
};
ref class C : B, I
{
public:
Hogenson_705-2C09.fm Page 238 Thursday, October 19, 2006 8:01 AM
CHAPTER 9

INTERFACES

239
// f implements I::f but doesn't override B::f
virtual void f() new
{
Console::WriteLine("C::f");
}
// g overrides B::g AND implements I::g
virtual void g() override
{
Console::WriteLine("C::g");
}
};
int main()
{
B^ b = gcnew B();
C^ c = gcnew C();
I^ i = c;
// behavior with the new specifier
b->f(); // calls B::f
c->f(); // calls C::f
i->f(); // calls C::f since C::f implements I::f
B^ bc = c; // b pointing to instance of C
bc->f(); // calls B::f since C::f is unrelated
// behavior with the override specifier
b->g(); // calls B::g
c->g(); // calls C::g
i->g(); // calls C::g since C::g implements I::g
bc->g(); // calls C::g since C::g overrides B::g
}
The output of Listing 9-5 is as follows:

B::f
C::f
C::f
B::f
B::g
C::g
C::g
C::g
Hogenson_705-2C09.fm Page 239 Thursday, October 19, 2006 8:01 AM
240
CHAPTER 9

INTERFACES
Interfaces with Properties and Events
Interfaces may have properties and events, but not fields. An implementing class must imple-
ment a trivial property’s get and set methods, or an event. This could occur by redeclaring the
trivial property or event in the implementing class, as in Listing 9-6.
Listing 9-6. Implementing Properties and Events
// interface_properties_events.cpp
using namespace System;
interface class I
{
property int P1;
event EventHandler^ E;
property int P2
{
int get();
void set(int v);
}
};

ref class R : I
{
int value;
public:
virtual property int P1;
virtual event EventHandler^ E;
virtual property int P2
{
int get() { return value; }
void set(int v) { value = v; }
}
};
Interface Name Collisions
Name conflicts can occur between interface methods and class methods or between methods
in multiple interfaces being implemented by the same class. In the case of a class that has a
method conflict with an interface, you use the explicit implementation syntax you saw in the
previous chapter to specify which method implements the interface method (see Listing 9-7).
Hogenson_705-2C09.fm Page 240 Thursday, October 19, 2006 8:01 AM
CHAPTER 9

INTERFACES
241
Listing 9-7. Disambiguating Name Collisions
// class_interface_method_ambiguity.cpp
using namespace System;
interface class IA
{
void f();
};
ref class A : IA

{
public:
// Note that new is not used here.
void f()
{
Console::WriteLine("A::f");
}
// explicit implementation syntax
virtual void fIA() = IA::f
{
Console::WriteLine("A::fIA implementing IA::f");
}
};
int main()
{
A^ a = gcnew A();
IA^ ia = a;
ia->f();
a->f();
}
Here is the output of Listing 9-7:
A::fIA implementing IA::f
A::f
As you can see, the method that gets called is determined by whether the method is accessed
through the interface or through the object. Now let’s turn to the case of a class implementing
two interfaces with the same name.
Inheritance in C++/CLI (and in other CLI languages such as C# and VB .NET) is different
from interface inheritance in some other languages, such as Java. The big difference between
the interface inheritance model in Java and the CLI is that CLI interfaces are independent of
each other, whereas in Java, interfaces can interfere with each other when name collisions

arise, such as when two or more interfaces implemented by the same type have methods with
Hogenson_705-2C09.fm Page 241 Thursday, October 19, 2006 8:01 AM
242
CHAPTER 9

INTERFACES
the same name. In the Java inheritance model, this is an ambiguity that must be resolved to a
single method. In the CLI inheritance model, both methods may be available on the type, and
you may access them both depending on what interface pointer you might be using. This rule
is like the rule used for interfaces in COM.
What this really means is that when you’re creating a class that implements two interfaces
that have methods with similar names, you don’t have to care about what potential name
conflicts might arise. In Java, it can be difficult to create one method that is a viable implemen-
tation of both interfaces. In CLI-based languages, both methods can coexist, and the interface
handle that is used determines which method is called.
Explicit interface implementation is the language construct that allows you to support
interfaces that have name conflicts. You create one method definition for each interface that
has the method, and you mark it in such a way that the compiler knows that it’s the version of
the method to be used when accessed through a given interface handle type. If it’s not being
accessed through an interface handle, but rather through a handle with the type of the object,
the calling code must resolve the ambiguity by specifying the interface in the call.
Consider the code in Listing 9-8.
Listing 9-8. Disambiguating by Specifying an Interface
// interface_name_collision.cpp
using namespace System;
interface class I1 { void f(); };
interface class I2 { void f(); };
ref class R : I1, I2
{
public:

virtual void f()
{ Console::WriteLine("R::f"); }
};
int main()
{
R^ r = gcnew R();
r->f(); // R::f() implements both I1's f and I2's f
}
The name conflict in Listing 9-8 is not an error, and the output is as you would expect:
R::f
In Listing 9-8, the function f in the class R implements both I1’s and I2’s version of f. This
might be desirable if the function that has the conflict has the same meaning in both interfaces,
Hogenson_705-2C09.fm Page 242 Thursday, October 19, 2006 8:01 AM
CHAPTER 9

INTERFACES
243
but if the interfaces have different notions of what f means and does, you need to explicitly
implement the functions inherited from each interface separately. The language provides
support for doing this, as in Listing 9-9.
Listing 9-9. Implementing Inherited Functions Separately
// explicit_interface_implementation.cpp
using namespace System;
interface class I1 { void f(); };
interface class I2 { void f(); };
ref class R : I1, I2
{
public:
virtual void f1() = I1::f
{

Console::WriteLine("R::f1 == I1::f");
}
virtual void f2() = I2::f
{
Console::WriteLine("R::f2 == I2::f");
}
};
int main()
{
R^ r = gcnew R();
I1^ i1 = r;
I2^ i2 = r;
r->f1(); // OK -- call through the object.
r->f2(); // OK -- call through the object.
// r->f(); // Error: f is not a member of R.
i1->f(); // OK -- call f1.
i2->f(); // OK -- call f2.
// r->I1::f(); // Compiler error: "direct call will fail at runtime".
// r->I1::f1(); // Error: f1 is not a member of I1.
}
The final two calls are not supported. The output of Listing 9-9 is as follows:
Hogenson_705-2C09.fm Page 243 Thursday, October 19, 2006 8:01 AM

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×