Open In App

Design Goals and Principles of Object Oriented Programming

Last Updated : 09 Mar, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

The fundamental goal of dealing with the complexity of building modern software naturally gives rise to several sub-goals. These sub-goals are directed at the production of quality software, including good implementations of data structures and algorithms. The article focuses on discussing design goals and principles of Object Oriented Programming. 

OOP Goals

The three goals of Object Oriented Programming are Robustness, Adaptability, and Reusability.

1. Robustness

Every good programmer wants to produce software that is correct, which means that a program produces the right output for all the anticipated inputs in the program’s application. In addition, we want the software to be robust, that is, capable of handling unexpected inputs that are not explicitly defined for its application. 

  • For example, if a program is expecting a positive integer (say, representing the price of an item) and instead is given a negative integer, then the program should be able to recover gracefully from this error. A program that does not gracefully handle such unexpected errors can be embarrassing for the programmer.
  • More importantly, in life-critical applications, where a software error can lead to injury or loss of life, software that is not robust is deadly. This importance was driven home in the late 1980s in accidents involving Therac-25, a radiation-therapy machine,  which severely overdosed six patients between 1985 and 1987, some of whom died from complications resulting from their radiation overdoses. All six accidents were traced to software errors, with one of the most troubling being a user-interface error involving unexpected inputs (a fast-typing radiologist could backspace over a radiation dosage on the screen without the previous characters actually being detected from the input).

The goal of robustness goes beyond the need to handle unexpected inputs, however. Software should produce correct solutions, even given the well-known limitations of computers. For example, if a user wishes to store more elements in a data structure than originally expected, then the software should expand the capacity of this structure to handle more elements. This philosophy of robustness is present, for example, in the vector class in C++’s Standard Template Library, which defines in the expandable array. In addition, if an application calls for numerical computations, those numbers should be represented fully and should not overflow or under-flow Indeed, software should achieve correctness for its full range of possible inputs, including boundary cases, such as when an integer value is 0 or 1 or the maximum or minimum possible values. Robustness and correctness do not come automatically, however, they must be designed from the start.

2. Adaptability

Modern software projects, such as word processors, web browsers, and Internet Search engines, typically involve large programs that are expected to last for many years. Therefore, software needs to evolve over time in response to changing conditions in its environment.

  • These changes can be expected, such as the need to adapt to an increase in CPU or network speed, or they can be unexpected, such as the need to add new functionality because of new market demands. 
  • Software should also be able to adapt to unexpected events that, in hindsight, really should have been expected, such as the coming of a new millennium and its effects on date calculations (the “the year 2000” problem). 
  • Another important goal of quality software is that it achieves adaptability (also called evolvability). Related to this concept is portability, which is the ability of software to run with minimal change on different hardware and operating system platforms. The fact that different implementations of C++ are allowed to implement some language features differently, such as the number of bits in a long integer, makes portability difficult to achieve.

3. Reusability

Going hand in hand with adaptability is the desire that software is reusable, that is, code should be usable as a component of different systems in various applications. Developing quality software can be an expensive enterprise, and its cost can be offset somewhat if the software is designed in a way that makes it easily reusable in future applications. Such reuse should be done with care, however. One of the major sources of software errors in the Therac-25 arose from the inappropriate reuse of software from the Therac-20 (which was not designed for the hardware platform used with the Theme-25). So, for software to be truly reusable, we must be clear about what it does and does not do. Given this clarity, however, software reuse can be a significant cost-saving and time-saving technique.

OOP Principles

These principles are guidelines intended for programmers to apply while working on software to remove buggy code. 

1. Single Responsibility Principle (SRP)

The Single Responsibility Principle (SRP) states that there should never be more than one reason for a class to change. This means that every class, or similar structure, in your code should have only one job to do. Simply, a class should have only a single responsibility.

  • The class should only have one and only one reason to be changed. When there is more than one responsibility, there is more than one reason to change that class at the same point. So, there should not be more than one separate functionality in that same class that may be affected.
  • For Example, If a class Sales keeps the information about a sales order, and in addition has a method saveOrder() that saves the Sales in a database and a method exportXML() that exports the Sales in XML format, this design will violate the SRP because there will be different types of users of this class and different reasons for making changes to this class. A change made for one type of user, says the change in the type of database, may require the re-test, recompilation, and re-linking of the class for the other type of user.
  • A better design will be to have the Sales class only keeps the information about a sales order, and have different classes to save the order and export order, respectively. Such a design will confirm with SRP.
  • The SRP principle helps us to deal with bugs and implement changes without confusing co-dependencies.

2. Open-Closed Principle (OCP)

The Open-Closed Principle states that classes should be open for extension but closed for modification. “Closed for modification” means that once you have developed a class you should never modify it, except to correct bugs. “Open to extension” means that you should design your classes in such a way that new functions will be generated as per new requirements are generated. Simply, “software entities like classes, modules, functions, should be open for extension, but closed for modification”.

  • For Example, Suppose an OrderValidate class has a method validate(Order order) that is programmed to validate an order based on a set of hard-coded rules. This design violates the OCP because if the rules change,  the OrderValidate class has to be modified, compiled, and tested.
  • A better design will be to let the OrderValidate class contain a collection of ValidationRule objects each of which has a validate(Order order) method (perhaps defined in a Validation interface) to validate an Order using a specific rule, and the validate(Order order) method of OrderValidate class can simply iterate through those ValidationRule objects to validate the order. The new design will satisfy the OCP because if the rules change, one can just create a new ValidationRule object and add it to an OrderValidation instance at run time (rather than to the class definition itself).

3. Liskov Substitution Principle (LSP)

The principle was introduced by Barbara Liskov in 1987 and according to this principle “Derived or child classes must be substitutable for their base or parent classes“. It applies to inheritance hierarchies, specifying that you should design your classes so that client dependencies can be substituted with subclasses without the client knowing about the change.

  • For Example, Suppose a Rectangle class has two instance variables height and width, and a method setSize(int a, int b), which set the height to a and width to b. Suppose Square is a subclass of Rectangle and it overrides the inherited method by setting both height and width to a. This design will violate LSP. 
  • To see this, consider a client using a reference variable of type Rectangle to call the setSize() method to assign different values of a and b, and then immediately verify if the sizes were set correctly or the area is correctly computed. 
  • The results will be different if the variable references a Rectangle object than a Square object. It turns out that in OO programming, a Square is not a Rectangle at all because it behaves differently from a Rectangle.

Generally, if a subtype of the supertype does something that the client of the supertype does not expect, then this is in violation of LSP.

4. Interface Segregation Principle (ISP)

This principle is the first principle that applies to Interfaces instead of classes in SOLID and it is similar to the single responsibility principle. It states that clients should not be forced to depend upon interface members they do not use. This means that the interface should have the minimal set of methods required to ensure functionality and be limited to only one functionality.

  • For Example, If we create a Burger interface, we don’t need to implement the cheese() method because cheese isn’t available for every Burger type. 

5. Dependency Inversion Principle (DIP)

DIP principle The DIP requires that high-level modules should not depend on low-level modules, both should depend on abstraction. Also, abstraction should not depend on details, details should depend on abstractions. Secondly, abstractions should not depend upon details; details should depend upon abstractions. The idea is that we isolate our class behind a boundary formed by the abstractions it depends on. If all the details behind those abstractions change, then our class is still safe. This helps keep coupling low and makes our design easier to change. DIP also allows us to test things in isolation.

  • For Example, Making a class Button associate with another class Lamp (because a Lamp has a Button) is a violation of DIP. Instead, a better design will be to associate an AbstractBut with an AbstractButClient and define a Button as a subclass of the AbstractBut and a Lamp as a subclass of the AbstractButClient.
  • Another example can be, making an EBookReader class use PDFBook class is a violation of DIP because it requires changing the EBookReader class to read other types of e-books. A better design is to let EBookReader use an interface EBook and let PDFBook and other types of e-book classes implement EBook. Now adding or changing e-book classes will not require any change to EBookReader class.

6. KISS – Keep It Simple, Stupid Principle

This principle suggests not involving complexity in the code and trying to avoid it as much as you can. This is because the more complex code is written, the more difficult it becomes to modify at any later point in time. Other acronyms are: Keep it short and simple, Keep it simple and smart, and Keep it simple and straightforward.

  • The basic idea is to achieve goals and solve problems by building solutions as simple as possible and as short as can be. 
  • It helps in solving problems quickly, maintainable code, easy to debug, and high-quality, human-readable code.
  • If someone writes messy code, defines functions with unnecessary lines of code, etc. it leads to a violation of the KISS principle.

7. DRY Principle

The DRY principle stands for the “Don’t Repeat Yourself” principle. Programmers tend to write lots of duplicate code intentionally or unintentionally. This principle forces us to avoid this habit. 

  • To follow the DRY principle, we can – reuse the code and never duplicate it, use functions, and recursions, and write code in appropriate layers, locations, and services.

Below is the Java program to demonstrate the DRY principle:

Java




public class Animal { 
       public void eat() { 
           System.out.println("Eating"); 
       
}
public class Dog extends Animal { 
    public void bark() { 
        System.out.println("Barks"); 
    
public class Cat extends Animal { 
    public void meow() { 
        System.out.println("Meows"); 
    
}


Though both cat and dogs have common functionality eat(), they speak differently. So we put common functionality eating in the parent class Animal and then extend the parent class to child classes Dog and Cat. Repeating the code is a violation of the DRY principle.

8. YAGNI – You Ain’t Gonna Need It Principle

YAGNI stands for You Ain’t Gonna Need It. It simply means, don’t use or write code for something, until you really find value in doing it. The programmer usually implements so many things that they really don’t need. They write so many lines of code that are actually not required or may be useless. This leads to a waste of time and energy for the programmer and can even financial losses in the company product. In short, this principle is against the building of any features which are not required at present. So that developer can save time and focus on other pieces or components of a code.

9. DI – Dependency Inversion or Dependency Injection

This principle revolves around the coupling. Coupling is the degree of connectivity among things, that is how your piece of code is connected to each other. Simply, if your code is talking about another piece of code, there is coupling. A great example could be traditional class-based inheritance. Inheritance actually increases coupling quite a bit. Now as per this principle, either remove or minimize dependency to the extent it could be. If you can’t remove all dependency then at least minimize it. In the context of object-oriented design, depending on a class is called tight coupling, whereas depending on an interface, is called loose coupling.

10. Composition Over Inheritance Principle

This principle helps us to implement flexible and maintainable code. It states that we should have to implement interfaces rather than extending the classes. We implement the inheritance when the class need to implement all the functionalities and the child class can be used as a substitute for our parent class.

In Java, there are different types of Inheritance: Single inheritance, Multilevel inheritance, Hierarchical inheritance, Multiple inheritances, and Hybrid inheritance. Learn more about inheritance.

Java




//Inheritance example
import java.io.*;
 
// Base or Super Class
class Employee{
    int salary = 45000;
      int leaves = 5;
}
 
// Inherited or Sub Class
class employeeOfTheMonth extends Employee {
    int benefits = 10000;
      int leaves = 7;
}
 
// Driver Class
class Gfg {
    public static void main(String args[])
    {
        employeeOfTheMonth e1 = new employeeOfTheMonth();
        System.out.println("Salary is: " + e1.salary);
          System.out.println("Benefits: " + e1.benefits);
          System.out.println("Leaves: " + e1.leaves);
    }
}


Following these principles and OOP principles, any developer can build good and efficient software and products which can help the community.



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads