0% found this document useful (0 votes)
310 views

Tips On Object Oriented Programming

The document discusses 5 tips for object-oriented programming techniques. The first tip is to avoid calling virtual functions from constructors, as the derived object is not fully constructed yet. The second tip is to preserve the basic properties like method signatures and semantics when overriding methods. The third tip is to beware of order of initialization problems that can cause unintuitive behavior. The fourth tip is to avoid switch/nested if-else statements based on object types and instead use polymorphism with virtual functions. The fifth tip is to avoid hiding names in different scopes as it reduces readability and can cause subtle defects. Examples are provided in C++, Java and C# to illustrate the techniques.

Uploaded by

sgganesh
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
310 views

Tips On Object Oriented Programming

The document discusses 5 tips for object-oriented programming techniques. The first tip is to avoid calling virtual functions from constructors, as the derived object is not fully constructed yet. The second tip is to preserve the basic properties like method signatures and semantics when overriding methods. The third tip is to beware of order of initialization problems that can cause unintuitive behavior. The fourth tip is to avoid switch/nested if-else statements based on object types and instead use polymorphism with virtual functions. The fifth tip is to avoid hiding names in different scopes as it reduces readability and can cause subtle defects. Examples are provided in C++, Java and C# to illustrate the techniques.

Uploaded by

sgganesh
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 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!

You might also like