Object-Oriented Programming (OOP) Notes

INTRODUCTION TO Object-Oriented Programming (OOP)

  • Object-Oriented Programming (OOP) is a programming paradigm that uses objects and classes to design software.
  • OOP helps organize code efficiently and makes it easier to reuse and maintain.

Why Use OOP?

  • Code Reusability: Write once, reuse multiple times.
  • Better Organization: Code is structured and modular.
  • Easier Maintenance: Fixing and updating code is simpler.
  • Encapsulation: Protects data and prevents accidental changes.
  • Scalability: Suitable for small and large projects.

BASIC CONCEPTS OF OOP

  • OBJECTS: An object is an entity that has properties (attributes) and behaviors (methods). It represents a real-world entity.
    • Example: A Car is an object.
      • Properties: Brand, Color, Speed
      • Behaviors: Drive, Brake, Honk
  • CLASSES: A class is a blueprint for creating objects. It defines the properties and methods that objects of that type will have.
  • PROPERTIES AND METHODS:
    • Properties: Represent characteristics of an object (e.g., color, size, name).
    • Methods: Define actions or behaviors an object can perform.
  • CREATING AND USING OBJECTS: Once a class is defined, we can create an object and assign values to its properties.
class Person
{
 public string Name;
 public int Age;
 public void Introduce()
 {
 Console.WriteLine($"Hello, my name is {Name} and I am {Age} years old.");
 }
}
class Program
{
 static void Main()
 {
 Person p = new Person();
 p.Name = "Alice";
 p.Age = 20;
 p.Introduce();
 }
}
// Output: Hello, my name is Alice and I am 20 years old.
  • Real-World Analogy of OOP: Think of a class as a blueprint for building houses. Each house built from that blueprint is an object. Even though houses may differ in paint color, they all have common features like walls, doors, and windows.

TERMS

  • Class: A blueprint for creating objects, defining properties and methods.
  • Object: An instance of a class with its own unique values and behaviors.
  • Encapsulation: Restricting direct access to certain data fields to ensure data integrity.
  • Abstraction: Hiding complex implementation details and exposing only essential features.
  • Inheritance: A mechanism where a class (child) derives properties and methods from another class (parent).
  • Polymorphism: The ability of different objects to respond to the same function call in different ways.
  • Method: A function defined within a class that performs an action.
  • Constructor: A special method in a class that initializes an object when it is created.
  • Destructor: A method that is automatically called to free resources when an object is destroyed.
  • Static: A keyword that makes a method or property belong to the class rather than an instance.
  • Access Modifier: Defines the scope of access to class members (e.g., public, private, protected).
  • Interface: A contract that a class must implement, containing method definitions but no implementation.
  • Namespace: A container for organizing classes and avoiding name conflicts.
  • Instance: A specific realization of an object created from a class.
  • Property: A special type of class member that allows controlled access to private fields.
  • Field: A variable declared within a class to store data.
  • Abstract Class: A class that cannot be instantiated and is meant to be inherited by other classes.
  • Override: A keyword used to modify a method in a derived class, replacing the base class method.
  • Overloading: Defining multiple methods with the same name but different parameters.
  • Sealed Class: A class that cannot be inherited.
  • Virtual Method: A method in a base class that can be overridden in derived classes.
  • Base Keyword: Used to refer to the parent class of a derived class.
  • This Keyword: Refers to the current instance of the class.
  • New Keyword: Used to create new instances of a class or hide a base class member.

C# ACCESS MODIFIERS AND ENCAPSULATION

  • ENCAPSULATION: Encapsulation means wrapping up data and methods into a single unit, like a capsule.
    • In object-oriented programming, it hides the internal details of how something works and only allows controlled access, protecting the data from direct changes.
    • Abstraction allows making relevant information visible, and encapsulation enables a programmer to implement the desired level of abstraction.
  • Encapsulation is implemented by using access specifiers.
    • An access specifier defines the scope and visibility of a class member.
  • ACCESS MODIFIER: Access modifiers in C# define the visibility and accessibility of classes, methods, and variables within an application.
    • They help in encapsulating data and restricting access to prevent unintended modifications.

C# ACCESS MODIFIER

  • public: The code is accessible for all classes.

  • private: The code is only accessible within the same class.

  • protected: The code is accessible within the same class, or in a class that is inherited from that class.

  • internal: The code is only accessible within its own assembly, but not from another assembly.

  • PRIVATE MODIFIER: A field declared with the private access modifier can only be accessed within its own class.

//Example
OUTPUT: Mustang
  • Trying to access a private field outside the class results in an error.
//Example of error
OUTPUT: 'Car.model' is inaccessible due to its protection level
  • PUBLIC MODIFIER: A field declared with the public access modifier is accessible by all classes.
class Car
{
 public string model = "Mustang";
}
class Program
{
 static void Main(string[] args)
 {
 Car myObj = new Car();
 Console.WriteLine(myObj.model);
 }
}
//OUTPUT: Mustang

WHY USE ACCESS MODIFIERS IN C#?

  • Access modifiers are essential in C# for several reasons, primarily focusing on encapsulation, security, and maintainability.
    • Encapsulation (Data Hiding):
      • Access modifiers help implement data hiding by restricting direct access to variables and methods.
      • Prevents accidental modification of sensitive data.
    • Security & Controlled Access:
      • Ensures that only specific parts of the program can modify critical data.
    • Code Maintainability & Modularity:
      • Helps in organizing code by restricting unnecessary access.
      • Makes it easier to debug and maintain large projects.
    • Prevents Accidental Changes:
      • Avoids unintended modifications from external classes or developers.
    • Supports Inheritance & Polymorphism:
      • Modifiers like protected allow a balance between security and flexibility in object-oriented programming.
  • By default, all members of a class are private if you don't specify an access modifier.
class Car
{
 string model; // private
 string year; // private
}

C# INHERITANCE

  • What is Inheritance?
    • Inheritance allows a class (child/derived) to acquire properties and behaviors (fields and methods) from another class (parent/base).
    • It helps in reusing code and creating hierarchical relationships between classes.
  • Derived and Base Class
    • A base class is a class that provides common attributes and methods which can be shared among derived classes.
    • It serves as a foundation for other classes to build upon.
    • A derived class is a class that inherits from a base class.
      • It can access the properties and methods of the base class, and it can also have its own additional properties and methods or override the base class methods to provide specific functionality.
class Vehicle // Base class
{
 public string Brand = "Generic Brand";
 public void Honk()
 {
 Console.WriteLine("Honk! Honk!");
 }
 public void StartEngine()
 {
 Console.WriteLine("Engine started.");
 }
}
class Car : Vehicle // Derived class
{
 public string ModelName = "Model-X";
 public void ShowDetails()
 {
 Console.WriteLine($"Brand: {Brand}, Model: {ModelName}");
 }
}
class Program
{
 static void Main()
 {
 Car myCar = new Car();
 myCar.Honk(); // Inherited method
 myCar.StartEngine(); // Inherited method
 myCar.ShowDetails(); // Own method
 }
}
//Output
//Honk! Honk!
//Engine started.
//Brand: Generic Brand, Model: Model-X

WHY USE INHERITANCE?

  • Reusability: Avoid rewriting code—use fields and methods from an existing class.
  • Organization: Group related functionality logically through class hierarchies.
  • Maintainability: Changes made in a base class automatically apply to derived classes, making code easier to update and debug.
  • Extensibility: Easily extend or override behaviors in derived classes.
  • The sealed Keyword: Use sealed to prevent a class from being inherited
    • The error message will be something like this: 'Car': cannot derive from sealed type 'Vehicle'

BRAIN TEASERS

  • Brain Teaser #1: Who’s the Parent?
    • Question: If we create an object of Puppy and call Speak(), will it work?
    • Answer: Yes! Because Puppy inherits from Dog, and Dog inherits from Animal. So Puppy indirectly inherits from Animal.
  • Brain Teaser #2: Fix the Bug!
    • Question: Why does this error happen, and how can we fix it?
    • Answer: myMachine is declared as Appliance, so it only has access to Appliance methods.
WashingMachine myMachine = new WashingMachine();
myMachine.StartWash();
  • Brain Teaser #3: The Hidden Method
class Instrument {
 public void Play () => Console.WriteLine("Playing sound...");
}
class Piano: Instrument {
 public void Presskeys() => Console.WriteLine("Keys pressed.");
}
class DigitalPiano : Piano {
 public void ConnectToApp() => Console.WriteLine("Connected to app.");
}
*   Question:
    *   What will happen if we write this in Main()?
Instrument myInstrument = new DigitalPiano();
myInstrument. Play();
myInstrument.ConnectToApp(); // x will this work?
*   Answer:
    *   `myInstrument.Play()` works — method is in `Instrument`.
    *   `myInstrument.ConnectToApp()` gives an error — declared as `Instrument`, so it doesn’t know about `DigitalPiano` methods.
  • Brain Teaser #4: Same Method, Different Output?
class Parent {
 public void ShowMessage() => Console.WriteLine("Parent message");
}
class Child: Parent {
 public new void ShowMessage() => Console.WriteLine("Child message");
}
*   Question:
    *   What will the output be for the
Child c = new Child();
c.ShowMessage();
Parent p = new Child();
p.ShowMessage();
*   Answer:
    *   The `new` keyword hides the base method. So calling `ShowMessage()` via `Parent` reference calls the base version.
  • Brain Teaser #5: Constructor Order Confusion
class Animal {
 public Animal() {
 Console.WriteLine("Animal created");
 }
}
class Cat : Animal {
 public Cat () {
 Console.WriteLine("Cat created");
 }
}
*   Question:
    *   What is the output when this runs?
Cat myCat = new Cat();
*   Answer:
Animal created
Cat created
    *   Even though you're creating a `Cat`, the base `Animal` constructor runs first automatically!

C# POLYMORPHISM

  • Derived from Greek: poly = many, morph = form.
  • One interface, multiple functions.
  • In C#, polymorphism is achieved through inheritance and method overriding.
  • What is Polymorphism?

TWO TYPES OF POLYMORPHISM

  • Compile Time Polymorphism / Static Polymorphism
  • Run-Time Polymorphism / Dynamic Polymorphism
  • Compile Time vs Runtime
    • Compile time refers to the phase when the code you’ve written is converted (compiled) into machine language before the program runs.
      • Happens before execution
      • Errors like syntax errors are caught here
    • Runtime is when the program is already running (after compiling), and the system is executing the instructions.
      • Happens during execution
      • Errors like dividing by zero or accessing null objects occur here
  • Compile Time Polymorphism / Static Polymorphism
    • The process of linking a function with an object at compile time is known as early binding, or static binding.
    • C# offers two methods to achieve static polymorphism:
      • Function overloading
      • Operator overloading
  • Function overloading allows you to create multiple methods in the same class with the same name but different signatures (i.e., different parameter types or counts).
    • This helps achieve polymorphism and makes the code more readable.
using System;
class Printer {
 public void Print (int number) {
 Console.WriteLine("Printing number: " + number);
 }
 public void Print(string text) {
 Console.WriteLine("Printing text: " + text);
 }
 public void Print (double number) {
 Console.WriteLine("Printing double: " + number);
 }
}
static void Main(string[] args) {
 Printer myPrinter = new Printer();
// Call Print to output an integer
 myPrinter.Print(10);
// Call Print to output a string
 myPrinter.Print ("Hello, World!");
// Call Print to output a double
 myPrinter.Print (3.14159);
// Wait for user input before closing
 Console.WriteLine("Press any key to exit...");
 Console.ReadKey();
}
  • Operator overloading allows you to define custom behavior for standard operators (like +, -, *, etc.) for user-defined types (such as classes or structs).
    • This enables intuitive use of operators with objects.
class Box {
 public double Length, Breadth, Height;
 public Box(double l, double b, double h) {
 Length = l; Breadth = b; Height = h;
 }
 public static Box operator +(Box b1, Box b2) {
 return new Box(b1.Length + b2.Length, b1.Breadth + b2.Breadth,
 b1.Height + b2.Height);
 }
}
  • Run-Time Polymorphism / Dynamic Polymorphism
    • This occurs when a method call is resolved at runtime, allowing derived classes to provide specific implementations of a method defined in a base class, commonly achieved through method overriding and interfaces.
    • Here are the rules about abstract classes
      • You cannot create an instance of an abstract class
      • You cannot declare an abstract method outside an abstract class
      • When a class is declared sealed, it cannot be inherited, abstract classes cannot be declared sealed.
abstract class Animal
{
 public abstract void MakeSound(); // Abstract method
 public void Sleep() // Regular method
 {
 Console.WriteLine("Sleeping...");
 }
}
class Dog : Animal
{
 public override void MakeSound() // Implementing the abstract
 {
 Console.WriteLine("Bark");
 }
}
class Program
{
 static void Main()
 {
 Animal myDog = new Dog(); // Instantiate derived class
 myDog.MakeSound(); // Output: Bark
 myDog.Sleep(); // Output: Sleeping...
 }
}