quiz Programming · 10 questions

Fundamentals of Object-Oriented Programming

help_outline 10 questions
timer ~5 min
auto_awesome AI-generated
0 / 10
Score : 0%
1

Which programming technique introduces the concept of grouping related procedures into separate modules, each with its own internal state?

2

In a statically typed OO language, what determines at compile time whether a method call is legal for a variable?

3

Consider a class hierarchy where class B extends class A. Which statement correctly describes method overriding?

4

Which of the following best explains why Java does not support multiple inheritance of classes?

5

When a class defines a static variable, how is that variable shared among instances of the class?

6

In the context of templates, what is meant by 'lazy instantiation'?

7

Which statement correctly distinguishes between abstraction and inheritance in OOP?

8

A class 'Point' overloads the operator<< for output. Which of the following is true about the friend function implementation?

9

In a polymorphic variable scenario, why might a call to pet.speak() be a compile‑time error even though the actual object is a Dog?

10

When designing a class hierarchy, what is the primary advantage of using an interface instead of a concrete superclass?

menu_book

Fundamentals of Object-Oriented Programming

Review key concepts before taking the quiz

Understanding Modular Programming and Object‑Oriented Design

Modern software development encourages modular programming, where a program is divided into independent units called modules. Each module encapsulates its own data and procedures, reducing the risk of unintended side‑effects caused by a single global state. This concept is a direct predecessor of object‑oriented programming (OOP), which extends modularity by coupling data with behavior inside objects that communicate through message passing. By grouping related procedures and state, developers achieve:

  • Improved readability and maintainability
  • Easier testing of isolated components
  • Reusability across different projects

In OOP, each class defines a blueprint for objects, and the class itself acts as a module with its own internal state. This shift from procedural to object‑oriented thinking is essential for building scalable applications.

Static Typing and Compile‑Time Method Resolution

In statically typed object‑oriented languages such as Java, C#, and C++, the compiler checks the legality of a method call before the program runs. The key factor is the static (declared) type of the variable, not the runtime type of the object it references. For example:

Animal a = new Dog(); // a's static type is Animal
a.makeSound(); // compiler looks for makeSound() in Animal

If makeSound() is defined in Animal, the call is legal, even though the actual object is a Dog. This compile‑time verification prevents many runtime errors and enables powerful IDE features such as autocomplete and refactoring.

Method Overriding in Class Hierarchies

When a subclass overrides a method from its superclass, it provides a new implementation that will be invoked on instances of the subclass. Correct overriding follows these rules:

  • The method must have the exact same signature (name, return type, and parameters) as the method in the superclass.
  • In languages like C# and Java, the subclass method is marked with the override keyword (or virtual in the base class) to signal the intention.
  • The access level cannot be more restrictive than the base method.

Incorrect approaches, such as using a different signature or the new keyword in C#, result in method hiding rather than true overriding, which can lead to confusing behavior.

Why Java Avoids Multiple Inheritance of Classes

Java deliberately restricts classes to single inheritance** to sidestep the infamous diamond problem. When a class inherits from two parents that share a common ancestor, ambiguity arises about which ancestor's fields or methods should be used. Java resolves this by allowing a class to implement multiple interfaces, which provide method signatures without concrete state, thus preserving the benefits of multiple inheritance without the associated conflicts.

Key advantages of this design choice include:

  • Clear and predictable method resolution order.
  • Simpler compiler implementation and faster compilation.
  • Encouragement of composition over inheritance, leading to more flexible designs.

Static Variables: Shared State Across Instances

A static variable belongs to the class itself rather than any individual object. The JVM (or equivalent runtime) allocates a single memory slot for the variable when the class is loaded. Consequently:

  • All instances read and write the same value.
  • The variable can be accessed via the class name (ClassName.field) as well as through instances, though the former is the recommended style.
  • Static fields are often used for constants, counters, or configuration data shared across the application.

Example in Java:

public class Counter {
    private static int total = 0; // shared by all Counter objects
    public Counter() { total++; }
    public static int getTotal() { return total; }
}

Lazy Instantiation of Templates (C++)

In C++, templates are a compile‑time mechanism for generic programming. Lazy instantiation means that the compiler generates code for a template only when a specific specialization is actually used. This approach offers several benefits:

  • Reduces compilation time and binary size because unused template code is never emitted.
  • Allows developers to write highly generic libraries without incurring a penalty for every possible type.
  • Enables SFINAE (Substitution Failure Is Not An Error) techniques to provide different implementations based on type traits.

Contrast this with eager instantiation, where the compiler would generate code for every possible combination, leading to bloated binaries and longer compile cycles.

Abstraction vs. Inheritance: Distinct OOP Pillars

Both abstraction and inheritance are fundamental, yet they serve different purposes:

  • Abstraction focuses on exposing only the essential features of an entity while hiding implementation details. Abstract classes and interfaces are typical tools for defining contracts that concrete classes must fulfill.
  • Inheritance enables code reuse by allowing a new class to acquire the properties and behavior of an existing class. It creates a hierarchical relationship that can be leveraged for polymorphic behavior.

In practice, you might define an Shape interface (abstraction) that declares a draw() method. Concrete classes like Circle and Rectangle then inherit from a common base or implement the interface, reusing shared logic while providing their own specific drawing code.

Friend Functions and Operator Overloading in C++

When a class overloads the stream insertion operator (operator<<) for output, the typical implementation uses a friend function. Declaring the function as a friend grants it access to the class's private and protected members without making it a member function. This design is preferred because:

  • The left‑hand operand of operator<< is an ostream, which cannot be a member of the user‑defined class.
  • Friend status preserves encapsulation while still allowing the function to read internal state.
  • The function usually returns the stream by reference, enabling chaining (e.g., std::cout << p1 << p2;).

Typical declaration:

class Point {
    private:
        int x, y;
    public:
        Point(int x, int y) : x(x), y(y) {}
        friend std::ostream& operator<<(std::ostream& os, const Point& pt);
};

std::ostream& operator<<(std::ostream& os, const Point& pt) {
    return os << '(' << pt.x << ", " << pt.y << ')';
}

This pattern demonstrates how friend functions bridge the gap between external operators and class internals.

Putting It All Together: A Mini‑Course Recap

By mastering the concepts covered above, you will be equipped to design robust, maintainable object‑oriented systems:

  • Use modular design to isolate state and behavior.
  • Leverage static typing for early error detection and clearer APIs.
  • Apply method overriding correctly to enable polymorphism.
  • Understand why Java avoids multiple inheritance and how interfaces fill the gap.
  • Employ static variables wisely for shared data.
  • Take advantage of lazy template instantiation to keep binaries lean.
  • Distinguish abstraction from inheritance to model real‑world problems effectively.
  • Implement friend functions for clean operator overloading in C++.

These principles form the backbone of professional software development and are frequently tested in technical interviews, certification exams, and real‑world code reviews. Continue practicing by writing small programs that combine several of these ideas—such as a hierarchy of geometric shapes that use static counters, abstract interfaces, and overloaded output operators—to solidify your understanding.

Stop highlighting.
Start learning.

Join students who have already generated over 50,000 quizzes on Quizly. It's free to get started.