Вы находитесь на странице: 1из 37

Object Oriented Programming -

Essential Techniques

S G Ganesh
sgganesh@gmail.com
Language Pragmatics

 A language is not just syntax and learning


a language isn’t just learning to program.
 Mastering a language requires good
understanding of language semantics,
pragmatics, traps and pitfalls with
considerable experience in programming
and design using that language.
Five Specific OO Tips and Techniques

 We’ll see 5 specific tips/techniques


 Based on understanding, experience and
usage of language features
 Tips are about pragmatics of using
language features
 The mistakes covered in the tips are errors
in usage
 Examples in C++ and Java (sometimes in
C#)
1. Avoid calling virtual functions in constructors

 Constructors do not support runtime


polymorphism fully as the derived objects are
not constructed yet when base class constructor
executes.

 So, avoid calling virtual functions from base-


class constructors, which might result in subtle
bugs in the code.
C++ resolves virtual function calls to base type
struct base {
base() {
vfun();
}
virtual void vfun() {
cout << “Inside base::vfun\n”;
}
};
struct deri : base {
virtual void vfun() {
cout << “Inside deri::vfun\n”;
}
};
int main(){
deri d;
}
C++ resolves virtual function calls to base type
struct base {
base() {
vfun();
}
virtual void vfun() {
cout << “Inside base::vfun\n”;
}
};
struct deri : base {
virtual void vfun() {
cout << “Inside deri::vfun\n”;
}
};
int main(){
deri d;
}

// prints:Inside base::vfun
Java/C# resolves virtual function calls dynamically

// Java example
class base {
public base() {
vfun();
}
public void vfun() {
System.out.println("Inside base::vfun");
}
}
class deri extends base {
public void vfun() {
System.out.println("Inside deri::vfun");
}
public static void main(String []s) {
deri d = new deri();
}
}
Java/C# resolves virtual function calls dynamically

// Java example
class base {
public base() {
vfun();
}
public void vfun() {
System.out.println("Inside base::vfun");
}
}
class deri extends base {
public void vfun() {
System.out.println("Inside deri::vfun");
}
public static void main(String []s) {
deri d = new deri();
}
}

// prints: Inside deri::vfun


In C++, pure virtual methods might get called

struct base {
base() {
base * bptr = this;
bptr->bar();
// even simpler ...
((base*)(this))->bar();
}
virtual void bar() =0;
};
struct deri: base {
void bar(){ }
};
int main() {
deri d;
}
In C++, pure virtual methods might get called

struct base {
base() {
base * bptr = this;
bptr->bar();
// even simpler ...
((base*)(this))->bar();
}
virtual void bar() =0;
};
struct deri: base {
void bar(){ }
};
int main() {
deri d;
}
// g++ output:
// pure virtual method called
// ABORT instruction (core dumped)
Dynamic method call in Java might lead to trouble
// Java code
class Base {
public Base() {
foo();
}
public void foo() {
System.out.println("In Base's foo ");
}
}
class Derived extends Base {
public Derived() {
i = new Integer(10);
}
public void foo() {
System.out.println("In Derived's foo " + i.toString() );
}
private Integer i;
}
class Test {
public static void main(String [] s) {
new Derived().foo();
}
}
Dynamic method call in Java might lead to trouble
// Java code
class Base {
public Base() {
foo();
}
public void foo() {
System.out.println("In Base's foo ");
}
}
class Derived extends Base {
public Derived() {
i = new Integer(10);
}
public void foo() {
System.out.println("In Derived's foo " + i.toString() );
}
private Integer i;
}
class Test {
public static void main(String [] s) {
new Derived().foo();
}
}
// this program fails by throwing a NullPointerException
2. Preserve the basic properties of methods while
overriding

 Overriding the methods incorrectly can result in


bugs and unexpected problems in the code.
 Adhering to Liskov’s Substitution Principle is
possible only when overriding is done properly.
 Make sure that the method signatures match
exactly while overriding is done
 Provide semantics similar to the base method in
the overridden method.
In C++, provide consistent default parameters

struct Base {
virtual void call(int val = 10)
{ cout << “The default value is :”<< endl; }
};

struct Derived : public Base {


virtual void call(int val = 20)
{ cout << “The default value is :”<< endl; }
};

// user code:
Base *b = new Derived;
b->call();
In C++, provide consistent default parameters

struct Base {
virtual void call(int val = 10)
{ cout << “The default value is :”<< endl; }
};

struct Derived : public Base {


virtual void call(int val = 20)
{ cout << “The default value is :”<< endl; }
};

// user code:
Base *b = new Derived;
b->call();

// prints:
// The default value is: 10
In Java, final might be removed while overriding

class Base {
public void vfoo(final int arg) {
System.out.println("in Base; arg = "+arg);
}
}
class Derived extends Base {
public void vfoo(int arg) {
arg = 0;
System.out.println("in Derived; arg = "+arg);
}
public static void main(String []s) {
Base b = new Base();
b.vfoo(10);
b = new Derived();
b.vfoo(10);
}
}
In Java, final might be removed while overriding

class Base {
public void vfoo(final int arg) {
System.out.println("in Base; arg = "+arg);
}
}
class Derived extends Base {
public void vfoo(int arg) {
arg = 0;
System.out.println("in Derived; arg = "+arg);
}
public static void main(String []s) {
Base b = new Base();
b.vfoo(10);
b = new Derived();
b.vfoo(10);
}
}
// prints:
// in Base; arg = 10
// in Derived; arg = 0
Provide consistent exception specification

struct Shape {
// can throw any exceptions
virtual void rotate(int angle) = 0;
// other methods
};

struct Circle : public Shape {


virtual void rotate(int angle) throw (CannotRotateException) {
throw CannotRotateException();
}
// other methods
};

// client code
Shape *shapePtr = new Circle();
shapePtr->rotate(10);
// program aborts!
3. Beware of order of initialization problems.

 Many subtle problems can happen


because of order of initialization issues.
 Avoid code that depends on particular
order of implementation as provided by the
compiler or the implementation.
In C++, such init can cause unintuitive results

// translation unit 1
int i = 10;

// translation unit 2
extern int i;
int j = i;
// j is 0 or 10?
// depends on the compiler/link line.
In Java, such init can cause unintuitive results

class Init {
static int j = foo();
static int k = 10;
static int foo() {
return k;
}
public static void main(String [] s) {
System.out.println("j = " + j);
}
}
In Java, such init can cause unintuitive results

class Init {
static int j = foo();
static int k = 10;
static int foo() {
return k;
}
public static void main(String [] s) {
System.out.println("j = " + j);
}
}

// prints
// j=0
4. Avoid switch/nested if-else based on types

 Programmers from structured


programming background tend to use
extensive use of control structures.
 Whenever you find cascading if-else
statements or switch statements checking
for types or attributes of different types to
take actions, consider using inheritance
with virtual method calls.
C# code to switch based on types

public enum Phone {


Cell = 0, Mobile, LandLine
}
// method for calculating phone-charges
public static double CalculateCharges(Phone phone, int seconds){
double phoneCharge = 0;
switch(phone){
case Phone.Cell:
// calculate charges for a cell
case Phone.Mobile:
// calculate charges for a mobile
case Phone.LandLine:
// calculate charges for a landline
}
return phoneCharge;
}
C# code with if-else using RTTI

abstract class Phone {


// members here
}
class Cell : Phone {
// methods specific to cells
}
// similar implementation for a LandLine
public static double CalculateCharges(Phone phone, int seconds){
double phoneCharge = 0;
if(phone is Cell){
// calculate charges for a cell
}
else if (phone is LandLine){
// calculate charges for a landline
}
return phoneCharge;
}
C# code: Correct solution using virtual functions

abstract class Phone {


public abstract double CalculateCharges(int seconds);
// other methods
}
class Cell : Phone {
public override double CalculateCharges(int seconds){
// calculate charges for a cell
}
}

// similar implementation for a LandLine


// Now let us calculate the charges for 30 seconds
Phone ph = new Cell ();
ph.CalculateCharges(30);
5. Avoid hiding of names in different scopes.

 Hiding of names in different scopes is


unintuitive to the readers of the code
 Using name hiding extensively can affect
the readability of the program.
 Its a convenient feature; avoid name
hiding as it can result in subtle defects and
unexpected problems.
Hiding of names can happen in different situations

 The name in the immediate scope can hide the


one in the outer scope (e.g. function args and
local variables)
 A variable in a inner block can hide a name from
outer block (no way to distinguish the two)
 Derived class method differs from a base class
virtual method of same name in its return type or
signature - rather it is hidden.
 Derived member having same name and
signature as the base-class non-virtual non-final
member; the base member is hidden (e.g. data
members)
C++ examples for name hiding

// valid in C++, error in Java/C#


void foo { // outer block
int x, y;
{ // inner block
int x = 10, y = 20;
// hides the outer x and y
}
}

// C++ Code
int x, y; // global variables x and y
struct Point {
int x, y; // class members x and y
Point(int x, int y); // function arguments x and y
};
C++/Java/C# example for a bug with hiding

// Bug in C++, Java and C#


Point(int x, int y) {
x = x;
y = y;
}

// C++
Point(int x, int y) {
this->x = x;
this->y = y;
}
// Java and C#
Point(int x, int y) {
this.x = x;
this.y = y;
}
C++: No overloading across scopes

struct Base {
void foo(int) {
cout<<"Inside Base::foo(int)";
}
};

struct Derived : public Base {


void foo(double) {
cout<<"Inside Derived::foo(double)";
}
};

Derived d;
d.foo(10);
C++: No overloading across scopes

struct Base {
void foo(int) {
cout<<"Inside Base::foo(int)";
}
};

struct Derived : public Base {


void foo(double) {
cout<<"Inside Derived::foo(double)";
}
};

Derived d;
d.foo(10);
// prints:
// Inside Derived::foo(double)
Java: Overloading across scopes!

class base {
public void foo(int i) {
System.out.println("In Base::foo(int)");
}
}

class deri extends base {


public void foo(double i) {
System.out.println("Inside deri::foo(double)");
}
public static void main(String []s) {
deri d = new deri();
d.foo(10);
}
}
Java: Overloading across scopes!

class base {
public void foo(int i) {
System.out.println("In Base::foo(int)");
}
}

class deri extends base {


public void foo(double i) {
System.out.println("Inside deri::foo(double)");
}
public static void main(String []s) {
deri d = new deri();
d.foo(10);
}
}
// prints: Inside Base::foo(int)
How to write robust code and avoid defects?

 Many of the language rules, semantics and


pragmatics are unintuitive
 What can help in detecting bugs early?
 Tools (but of limited extent)
 Extensive testing
 Peer review
 Good knowledge and experience
 No other approach can create robust code than
passion towards writing excellent code
Q&A
Thank you!

Вам также может понравиться