[go: up one dir, main page]

0% found this document useful (0 votes)
15 views7 pages

21mis1162 Swe2019 Da-2

Download as pdf or txt
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 7

SWE2019 – Design Patterns

Digital Assignment 2

Cynthia Elisheba Vincent 21MIS1162

Elaborate on the statement “Program for an interface rather than an implementation.”

Programming to an Interface, Not an Implementation is a core principle in software design that


enhances both flexibility and maintainability. By focusing on interfaces instead of specific
implementations, developers can decouple systems from their dependencies, resulting in more modular,
testable, and scalable code. This approach aligns with essential design principles such as the Open/Closed
Principle, which facilitates easy extensions of systems without modifying existing code.

Adopting the principle of programming to an interface is vital for developing software that is flexible,
maintainable, and scalable. By minimizing reliance on particular implementations, developers can swiftly
adapt to changes, incorporate new features, and ensure that their systems can accommodate future
requirements. This practice not only encourages superior design but also contributes to the creation of
more resilient and reusable code.

By embracing this principle, developers can construct software that is simpler to manage and extend,
ultimately resulting in higher-quality products. Prioritizing interfaces over specific implementations,
developers decouple systems from their dependencies, leading to more modular, testable, and scalable
code.

Key Benefits

1. Decoupling: This approach allows developers to change implementations without affecting the
clients that rely on them. As noted in Robert C. Martin's Clean Architecture, decoupling helps
minimize the impact of changes on the overall system.
2. Adherence to Design Principles: This principle aligns with the Open/Closed Principle, which
states that software entities should be open for extension but closed for modification. By
designing interfaces, systems can be extended with new functionality without altering existing
code, thereby enhancing system robustness.
3. Improved Testability: Coding to interfaces makes unit testing easier, as developers can
substitute different implementations or mock objects during testing. As discussed in The
Pragmatic Programmer by Andrew Hunt and David Thomas, this leads to better test coverage
and simpler tests.
4. Enhanced Flexibility and Scalability: By adopting this principle, developers can more easily
adapt to changes in requirements or introduce new features. Systems designed with interfaces can
evolve more seamlessly over time, as emphasized in Martin Fowler’s Refactoring: Improving the
Design of Existing Code.

The Factory Method pattern from the Gang of Four (GoF) design patterns implements the principle of
programming to an interface rather than an implementation. This pattern defines an interface for creating
an object but allows subclasses to alter the type of objects that will be created.
Discuss in detail Sub-classing Vs Sub-typing

Subclassing Subtyping
The process of creating a new class derived A relationship where one type can replace
from an existing class to inherit and enhance another type without altering the correctness of
its features. the program.
Concentrates on class inheritance and the Concentrates on ensuring type compatibility
formation of class hierarchies. and supporting polymorphic behavior.
Involves sharing attributes and methods Does not always require inheritance; it can
through inheritance. include the implementation of interfaces.
Involves establishing new classes that Can involve any type that adheres to a defined
extend existing ones. interface or abstract class.
Allows for changing behavior through Enables different implementations to be
method overriding. treated as if they were the same type.
May result in complicated class hierarchies Simplifies the use of types, improving code
that can complicate maintenance. flexibility and ease of maintenance.
Typically determined at compile time Can be assessed at both compile time (through
through class hierarchies. interface validation) and runtime (via dynamic
dispatch).
Sublclassing
Subclassing is the process of creating a new class, called a subclass, from an existing class, known as the
superclass. Subclasses inherit attributes and methods from the superclass, but they can also add or
override specific methods to modify or extend functionality. Customizing an object through subclassing;
each new class introduces some overhead, including setup and teardown steps. Creating a subclass
requires understanding of the parent class; for instance, overriding one method may require modifications
to others, and sometimes an overridden method may need to call a method from the parent class.
Additionally, subclassing can lead to a rapid increase in the number of classes, as even minor extensions
may require creating multiple new subclasses.
class Vehicle {
String brand;

public Vehicle(String brand) {


this.brand = brand;
}

public String startEngine() {


return "Engine started.";
}
}
class Car extends Vehicle {
String model;

public Car(String brand, String model) {


super(brand); // Call the constructor of the parent class
this.model = model;
}

@Override
public String startEngine() {
return brand + " " + model + " engine started with a roar!";
}
}
public class Main {
public static void main(String[] args) {
Car myCar = new Car("Toyota", "Corolla");
System.out.println(myCar.startEngine());
}
}

Vehicle Class This base class represents a generic vehicle with a brand attribute and a method
startEngine().

Car Subclass This subclass extends Vehicle, adds a model attribute, and overrides startEngine() to
provide a customized message.

Usage When we create an instance of Car and call startEngine(), it uses the overridden method in the Car
class
Subtyping

Interface inheritance (or subtyping) describes when an object can be used in place of another. A specific
data type, is associated with a supertype through the principle of substitutability. This means that program
elements like functions and methods designed for the supertype will still work when provided with the
subtype. Subtypes play a crucial role in object-oriented programming, as they can be used
interchangeably with their supertypes and may even impose stricter requirements than the supertype.

interface Payment {
void processPayment(double amount);
}
class CreditCardPayment implements Payment {
public void processPayment(double amount) {
System.out.println("Processing credit card payment of $" + amount);
}
}
class PayPalPayment implements Payment {
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount);
}
}
public class Main {
public static void main(String[] args) {
Payment ccPayment = new CreditCardPayment();
Payment ppPayment = new PayPalPayment();

ccPayment.processPayment(100.0); // Output: Processing credit card payment of $100.0


ppPayment.processPayment(150.0); // Output: Processing PayPal payment of $150.0
}
}

Supertype An interface called Payment that declares a method processPayment(double amount).

Subtypes Two classes, CreditCardPayment and PayPalPayment, implement the Payment interface,
each providing their specific implementation of processPayment().

Usage In the Main class, instances of CreditCardPayment and PayPalPayment are created as Payment
types, demonstrating the substitutability of subtypes.
Provide FIVE examples for Non-GoF Design Patterns and briefly explain them

Multiton

Definition
The Multiton pattern ensures that a specific number of instances are globally accessible, extending the
Singleton pattern's concept.

Usage
This pattern is used when a predefined number of instances of a class is necessary, allowing clients to
access them from a well-defined entry point.

Double-Checked Locking

Definition
The Double-Checked Locking pattern reduces the frequency of lock acquisitions by checking the locking
condition beforehand, leading to enhanced performance.

Usage
If only a single check is performed, another thread might create a new instance while the current thread is
still creating it. Implementing two checks yields similar results to a single check. Checking for null after
acquiring a lock could result in overwriting an existing instance. While checking first and then locking
works, it can be inefficient due to frequent locking.

public class Singleton {

private Singleton() {}

private static volatile Singleton INSTANCE;


public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}

}
Thread Pool

Definition
The Thread Pool pattern consists of a fixed number of threads (N) that execute a variable number of jobs
(M) concurrently.

Usage
The performance of the system is affected by how threads are created and destroyed. Creating too many
threads wastes resources, while destroying too many can lead to delays when recreating them. Slow
thread creation may result in poor client performance due to long wait times, and slow destruction may
deplete resources from other processes.

Mock Object

Definition
A Mock Object serves as a substitute for a real object, simulating its behavior in a controlled manner.

Usage
Using mock objects reduces or eliminates dependencies, speeds up tests by avoiding actual database
interactions, and can highlight design weaknesses when testing becomes challenging.
Dependency Injection

Definition
Dependency Injection is a technique that decouples a class from its dependencies, allowing for
independent management of object creation and usage.

Usage
A class can obtain its required objects through:

Direct construction of its dependencies.


Acquisition from external sources.
Parameters supplied during instantiation.

The two main methods of dependency injection are:

Constructor Injection: Dependencies are provided to the constructor.


Field Injection (Setter Injection): Dependencies are assigned after class creation.

Pros and Cons


Dependency injection enhances reusability and simplifies refactoring. However, it can lead to significant
boilerplate code in larger applications, especially when connecting dependencies in multi-layered
architectures.

You might also like