220 likes | 330 Views
Expressing Common Behavior. Example: General Purpose Interface Bus (GPIB) Interchangeable Hardware vs. Special-Purpose Software Interface Base Classes Create Objects, Use Interfaces Multiple Interfaces Interfaces as Components. Forms of Inheritance.
E N D
Expressing Common Behavior • Example: General Purpose Interface Bus (GPIB) • Interchangeable Hardware vs. Special-Purpose Software • Interface Base Classes • Create Objects, Use Interfaces • Multiple Interfaces • Interfaces as Components
Forms of Inheritance • Specialization: The derived class is a subtype, a special case of the base class. • Specification: The base class defines behavior that is implemented in the derived class. • Construction: The derived class makes use of the behavior provided by the • base class, but is not a subtype of the base class. • Generalization: The derived class modifies some of the methods of the base. • Extension: The derived class adds new functionality to the base, but does not • change any inherited behavior. • Limitation: The derived class restricts the use of some of the behavior • inherited from the base. • Variance: The derived class and the base class are variants of each other, • and the base/derived class relationship is arbitrary. • Combination: The derived class inherits from more than one base class.
Example: General Purpose Interface Bus (GPIB) An Acme 130 voltage supply GPIB A VoltyMetrics voltmeter
class GPIBController_Stub { • public: • void insert(const char* device_name, unsigned int address) { • cout << "(" << device_name << " now at address " << address • << ")" << endl; • } • void send(unsigned int address, const char* cmd) { • cout << "(" << "GPIB instrument #" << address << " sends " • << cmd << ")" << endl; • } • void send(unsigned int address, float f) { • cout << "(" << "GPIB instrument #" << address • << " sends value " << f << ")" << endl; • } • float receive(unsigned int address) { • cout << "(" << "Please enter number for GPIB instrument #" • << address << ": "; • float f; • cin >> f; • cout << ")" << endl; • return f; • } • }; GPIB Protocol Instead of bits on a GPIB cable we write bytes on cout ...
Class for Acme 130 voltage supply • class Acme130 { • public: • Acme130(GPIBController_Stub& controller, int gpib_address); • void set(float volts); • double minimum() const; • double maximum() const; • private: • GPIBController_Stub my_controller; • int my_gpib_address; • }; • An object has identity, state and behavior. Declare member data private.
Using an Acme 130 object • Acme130::Acme130(GPIBController_Stub& controller, int gpib_address) : • my_controller(controller), • my_gpib_address(gpib_address) { • my_controller.insert("Acme130", gpib_address); • } • void Acme130::set(float volts) { • if (volts > maximum() || volts < minimum()) { • throw "Acme 130 voltage out of range"; • } • my_controller.send(my_gpib_address, volts); • } • GPIBController_Stub gpip; • Acme130 volt_supply(gpip, 12); • volt_supply.set(3.6); Output: (Acme130 now at address 12) GPIB instrument #12 sends value (3.6)
An Acme 130 voltage supply GPIB A VoltyMetrics voltmeter Hardware is Interchangeable Hey, can I borrow your power supply?
Special-Purpose Software • float checkCalibration(Acme130& supply, VoltyMetrics& meter, • float tst_voltage) { • // Relative error at specified test voltage. • supply.set(tst_voltage); • return abs(tst_voltage - meter.read()) / tst_voltage; • } • VoltyMetrics meter(gpip, 14); • Acme130 supply1(gpip, 12); • ... • cout << "Acme130 rel. error at 1 volt is:" • << checkCalibration(supply1, meter, 1.0) << endl; • VoltOn59 supply2(gpip, 17); • ... • cout << "VoltOn59 rel. error at 1 volt is:" • << checkCalibration(supply2, meter, 1.0) << endl; Both Acme130 and VoltOn59 act like voltage supplies, but we did not say that in our code
Interface Base Classes A pure specification: An abstraction representing voltage supplies. • class VoltageSupply { • public: • virtual void set(float volts) = 0; • virtual float minimum() const = 0; • virtual float maximum() const = 0; • virtual ~VoltageSupply(); • }; • VoltageSupply::~VoltageSupply() {} Declare a virtual destructor in every interface base class unless it is derived from another interface base class that provides a virtual destructor.
float checkCalibration(VoltageSupply& supply, VoltyMetrics& meter, • float tst_voltage) { • // Relative error at specified test voltage. • supply.set(tst_voltage); • return abs(tst_voltage - meter.read()) / tst_voltage; • } Calibrate voltage supplies, not a particular supply.
Attaching Interfaces to Objects Attached to an interface via virtual derivation. • class Acme130_VS : • public VoltageSupply { • public: • Acme130_VS(GPIBController_Stub& controller, int gpib_address); • virtual void set(float volts); • virtual float minimum() const; • virtual float maximum() const; • private: • GPIBController_Stub my_controller; • int my_gpib_address; • }; An implementation: these virtual functions are defined.
Different class, same inteface base. • class VoltOn59_VS : • public VoltageSupply { • public: • VoltOn_59_VS(GPIBController_Stub& controller, int gpib_address); • virtual void set(float volts); • virtual float minimum() const; • virtual float maximum() const; • private: • GPIBController_Stub my_controller; • int my_gpib_address; • }; Redeclare virtual functions as virtual.
Create Objects; Use Interfaces • VoltyMetrics meter(gpib, 14); • Acme130_VS supply1(gpib, 12); • VoltOn59_VS supply2(gpib, 13); • cout << "Acme130_VS relative error at 1 volt is: " • << checkCalibration(supply1, meter, 1.0) << endl; • cout << "VoltOn59_VS relative error at 1 volt is: " • << checkCalibration(supply2, meter, 1.0) << endl; An Acme130_VS is created, checkCalibration uses VoltageSupply
"abstract" VoltageSupply void set(float) float minimum() float maximum() Acme130_VS VoltOn59_VS
Properties of Interface Base Classes: • Interface base classes are not a C++ feature • They are a usage idom 1. No data members 2. Function members only calling members 3. No constructor 4. Remaining functions pure (=0) virtual • Interface bases are abstract • Abstract classes allowed to have data and constructors 5. An virtual destructor with empty body
Multiple Interfaces • class GPIBInstrument { • public: • virtual void send(const char*) = 0; // Command. • virtual void send(float f) = 0; // Command with value. • virtual float receive() = 0; // Data point. • virtual ~GPIBInstrument(); • }; • // Read data from one GPIB instrument and send it to another one • void transferOnGPIB(GPIBInstrument& from, GPIBInstrument& to) { • to.send( from.receive() ); • } Same functions as individual GPIB instruments have to have, abstracted. Using only the abstraction we can write client code.
Attaching Multiple Interfaces Two interfaces, it's a VoltageSupply and it's a GPIBInstrument. • class Acme130_VS_GI : • public VoltageSupply, • public GPIBInstrument { • public: • Acme130_VS_GI(GPIBController_Stub& controller, int gpib_address); • // VoltageSupply interface • virtual void set(float volts); • virtual float minimum() const; • virtual float maximum() const; • // GPIBInstrument interface • virtual void send(const char*); // Command. • virtual void send(float f); // Command with value. • virtual float receive(); // Data point. • private: • GPIBController_Stub my_controller; • int my_gpib_address; • };
Voltmeter GPIBInstrument VoltageSupply VoltOn59 VoltyMetrics Acme130
Interfaces as Components • class GPIBController { • public: • virtual void insert(const char* device_name, unsigned int address)=0; • virtual void send(unsigned int address, const char* cmd) = 0; • virtual void send(unsigned int address, float f) = 0; • virtual float receive(unsigned int address) = 0; • virtual ~GPIBController(); • }; Abstraction of GPIBController_Stub functions
An Interfaced GPIBController • class GPIBController_GC : • public GPIBController { • public: • virtual void insert(const char* device_name, unsigned int address) { • cout << "(" << device_name << " now at address " << address • << ")" << endl; • } • virtual void send(unsigned int address, const char* cmd) { • cout << "(" << "GPIB instrument #" << address << " sends " • << cmd << ")" << endl; • } • virtual void send(unsigned int address, float f) { • cout << "(" << "GPIB instrument #" << address << " sends value " • << f << ")" << endl; • } • .... • } Interface Same functions, same implementation, now virtual.
Attaching Multiple Interfaces • class Acme130_VS_GI_GC : • public VoltageSupply, • public GPIBInstrument { • public: • Acme130_VS_GI_GC(GPIBController& controller, int gpib_address); • // VoltageSupply interface • virtual void set(float volts); • virtual float minimum() const; • virtual float maximum() const; • // GPIBInstrument interface • virtual void send(const char*); • virtual void send(float f); • virtual float receive(); • private: • GPIBController& my_controller; • int my_gpib_address; • }; GPIBController What is in the box? Some GPIBController. Which one? Who cares?
Expressing Common Behavior • Recognizing "is-usable-as" commonality • Interface base classes are the C++ idiom to represent common behavior • The action to be performed is determined by the derived class, not by the base class • Function clients call the interface through references or pointers • Class clients delay calls to interface functions by holding references or pointers