Am avut 371734 vizite de la lansarea siteului.




Inapoi        Inainte       Cuprins

Choosing composition vs. inheritance

Both composition and inheritance place subobjects inside your new class. Both use the constructor initializer list to construct these subobjects. You may now be wondering what the difference is between the two, and when to choose one over the other.

Composition is generally used when you want the features of an existing class inside your new class, but not its interface. That is, you embed an object to implement features of your new class, but the user of your new class sees the interface you’ve defined rather than the interface from the original class. To do this, you follow the typical path of embedding private objects of existing classes inside your new class.

Occasionally, however, it makes sense to allow the class user to directly access the composition of your new class, that is, to make the member objects public. The member objects use access control themselves, so this is a safe thing to do and when the user knows you’re assembling a bunch of parts, it makes the interface easier to understand. A Car class is a good example:

//: C14:Car.cpp
// Public composition

class Engine {
public:
  void start() const {}
  void rev() const {}
  void stop() const {}
};

class Wheel {
public:
  void inflate(int psi) const {}
};

class Window {
public:
  void rollup() const {}
  void rolldown() const {}
};

class Door {
public:
  Window window;
  void open() const {}
  void close() const {}
};

class Car {
public:
  Engine engine;
  Wheel wheel[4];
  Door left, right; // 2-door
};

int main() {
  Car car;
  car.left.window.rollup();
  car.wheel[0].inflate(72);
} ///:~

Because the composition of a Car is part of the analysis of the problem (and not simply part of the underlying design), making the members public assists the client programmer’s understanding of how to use the class and requires less code complexity for the creator of the class.

With a little thought, you’ll also see that it would make no sense to compose a Car using a “vehicle” object – a car doesn’t contain a vehicle, it is a vehicle. The is-a relationship is expressed with inheritance, and the has-a relationship is expressed with composition.

Subtyping

Now suppose you want to create a type of ifstream object that not only opens a file but also keeps track of the name of the file. You can use composition and embed both an ifstream and a string into the new class:

//: C14:FName1.cpp
// An fstream with a file name
#include "../require.h"
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

class FName1 {
  ifstream file;
  string fileName;
  bool named;
public:
  FName1() : named(false) {}
  FName1(const string& fname) 
    : fileName(fname), file(fname.c_str()) {
    assure(file, fileName);
    named = true;
  }
  string name() const { return fileName; }
  void name(const string& newName) {
    if(named) return; // Don't overwrite
    fileName = newName;
    named = true;
  }
  operator ifstream&() { return file; }
};

int main() {
  FName1 file("FName1.cpp");
  cout << file.name() << endl;
  // Error: close() not a member:
//!  file.close();
} ///:~

There’s a problem here, however. An attempt is made to allow the use of the FName1 object anywhere an ifstream object is used by including an automatic type conversion operator from FName1 to an ifstream&. But in main, the line

file.close();

will not compile because automatic type conversion happens only in function calls, not during member selection. So this approach won’t work.

A second approach is to add the definition of close( ) to FName1:

void close() { file.close(); }

This will work if there are only a few functions you want to bring through from the ifstream class. In that case you’re only using part of the class, and composition is appropriate.

But what if you want everything in the class to come through? This is called subtyping because you’re making a new type from an existing type, and you want your new type to have exactly the same interface as the existing type (plus any other member functions you want to add), so you can use it everywhere you’d use the existing type. This is where inheritance is essential. You can see that subtyping solves the problem in the preceding example perfectly:

//: C14:FName2.cpp
// Subtyping solves the problem
#include "../require.h"
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

class FName2 : public ifstream {
  string fileName;
  bool named;
public:
  FName2() : named(false) {}
  FName2(const string& fname)
    : ifstream(fname.c_str()), fileName(fname) {
    assure(*this, fileName);
    named = true;
  }
  string name() const { return fileName; }
  void name(const string& newName) {
    if(named) return; // Don't overwrite
    fileName = newName;
    named = true;
  }
};

int main() {
  FName2 file("FName2.cpp");
  assure(file, "FName2.cpp");
  cout << "name: " << file.name() << endl;
  string s;
  getline(file, s); // These work too!
  file.seekg(-200, ios::end);
  file.close();
} ///:~

Now any member function available for an ifstream object is available for an FName2 object. You can also see that non-member functions like getline( ) that expect an ifstream can also work with an FName2. That’s because an FName2 is a type of ifstream; it doesn’t simply contain one. This is a very important issue that will be explored at the end of this chapter and in the next one.

private inheritance

You can inherit a base class privately by leaving off the public in the base-class list, or by explicitly saying private (probably a better policy because it is clear to the user that you mean it). When you inherit privately, you’re “implementing in terms of;” that is, you’re creating a new class that has all of the data and functionality of the base class, but that functionality is hidden, so it’s only part of the underlying implementation. The class user has no access to the underlying functionality, and an object cannot be treated as a instance of the base class (as it was in FName2.cpp).

You may wonder what the purpose of private inheritance is, because the alternative of using composition to create a private object in the new class seems more appropriate. private inheritance is included in the language for completeness, but if for no other reason than to reduce confusion, you’ll usually want to use composition rather than private inheritance. However, there may occasionally be situations where you want to produce part of the same interface as the base class and disallow the treatment of the object as if it were a base-class object. private inheritance provides this ability.

Publicizing privately inherited members

When you inherit privately, all the public members of the base class become private. If you want any of them to be visible, just say their names (no arguments or return values) in the public section of the derived class:

//: C14:PrivateInheritance.cpp
class Pet {
public:
  char eat() const { return 'a'; }
  int speak() const { return 2; }
  float sleep() const { return 3.0; }
  float sleep(int) const { return 4.0; }
};

class Goldfish : Pet { // Private inheritance
public:
  Pet::eat; // Name publicizes member
  Pet::sleep; // Both overloaded members exposed
};

int main() {
  Goldfish bob;
  bob.eat();
  bob.sleep();
  bob.sleep(1);
//! bob.speak();// Error: private member function
} ///:~

Thus, private inheritance is useful if you want to hide part of the functionality of the base class.

Notice that giving the name of an overloaded function exposes all the versions of the overloaded function in the base class.

You should think carefully before using private inheritance instead of composition; private inheritance has particular complications when combined with runtime type identification (this is the topic of a chapter in Volume 2 of this book, downloadable from www.BruceEckel.com).

protected

Now that you’ve been introduced to inheritance, the keyword protected finally has meaning. In an ideal world, private members would always be hard-and-fast private, but in real projects there are times when you want to make something hidden from the world at large and yet allow access for members of derived classes. The protected keyword is a nod to pragmatism; it says, “This is private as far as the class user is concerned, but available to anyone who inherits from this class.”

The best approach is to leave the data members private – you should always preserve your right to change the underlying implementation. You can then allow controlled access to inheritors of your class through protected member functions:

//: C14:Protected.cpp
// The protected keyword
#include <fstream>
using namespace std;

class Base {
  int i;
protected:
  int read() const { return i; }
  void set(int ii) { i = ii; }
public:
  Base(int ii = 0) : i(ii) {}
  int value(int m) const { return m*i; }
};

class Derived : public Base {
  int j;
public:
  Derived(int jj = 0) : j(jj) {}
  void change(int x) { set(x); }
}; 

int main() {
  Derived d;
  d.change(10);
} ///:~

You will find examples of the need for protected in examples later in this book, and in Volume 2.

protected inheritance

When you’re inheriting, the base class defaults to private, which means that all of the public member functions are private to the user of the new class. Normally, you’ll make the inheritance public so the interface of the base class is also the interface of the derived class. However, you can also use the protected keyword during inheritance.

Protected derivation means “implemented-in-terms-of” to other classes but “is-a” for derived classes and friends. It’s something you don’t use very often, but it’s in the language for completeness.

Home   |   Web Faq   |   Radio Online   |   About   |   Products   |   Webmaster Login

The quality software developer.™
© 2003-2004 ruben|labs corp. All Rights Reserved.
Timp de generare a paginii: 17583 secunde
Versiune site: 1.8 SP3 (build 2305-rtm.88542-10.2004)