Design Pattern - Advanced
Design Pattern - Advanced
1. Composite Pattern:
The Composite pattern allows you to treat individual objects and groups of objects uniformly. It enables the creation of hierarchical structures, where a group of objects can be treated as a single object.
```java
import java.util.ArrayList;
import java.util.List;
interface Graphic {
void draw();
}
class Circle implements Graphic {
public void draw() {
System.out.println("Drawing a circle");
}
}
class CompositeGraphic implements Graphic {
private List<Graphic> graphics = new ArrayList<>();
public void add(Graphic graphic) {
graphics.add(graphic);
}
public void draw() {
for (Graphic graphic : graphics) {
graphic.draw();
}
}
}
public class CompositePatternExample {
public static void main(String[] args) {
Circle circle1 = new Circle();
Circle circle2 = new Circle();
CompositeGraphic composite = new CompositeGraphic();
composite.add(circle1);
composite.add(circle2);
composite.draw();
// Output:
// Drawing a circle
// Drawing a circle
}
}
```
Explanation: In this example, we have a `Graphic` interface that defines the `draw` method. The `Circle` class implements the `Graphic` interface, representing an individual object. The `CompositeGraphic` class also implements the `Graphic` interface and contains a list of `Graphic` objects. When the `draw` method is called on the `CompositeGraphic`, it invokes the `draw` method on each of its child `Graphic` objects.
2. Proxy Pattern:
The Proxy pattern provides a surrogate or placeholder for another object and controls access to it. It can be used to add additional functionality to an object or to control its instantiation.
```java
interface Image {
void display();
}
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading image: " + filename);
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
class ImageProxy implements Image {
private String filename;
private RealImage image;
public ImageProxy(String filename) {
this.filename = filename;
}
public void display() {
if (image == null) {
image = new RealImage(filename);
}
image.display();
}
}
public class ProxyPatternExample {
public static void main(String[] args) {
Image image = new ImageProxy("image.jpg");
image.display();
// Output:
// Loading image: image.jpg
// Displaying image: image.jpg
}
}
```
Explanation: In this example, the `Image` interface defines the `display` method. The `RealImage` class implements the `Image` interface and represents the real object that is expensive to create. The `ImageProxy` class also implements the `Image` interface and acts as a proxy to the `RealImage`. The `ImageProxy` is responsible for creating the `RealImage` object only when necessary, and delegates the `display` method to the `RealImage`.
3. Adapter Pattern:
The Adapter pattern allows objects with incompatible interfaces to work together by providing a common interface. It converts the interface of one class into another interface that clients expect.
```java
interface MediaPlayer {
void play(String audioType, String filename);
}
interface AdvancedMediaPlayer {
void playVlc(String filename);
void playMp4(String filename);
}
class VlcPlayer implements AdvancedMediaPlayer {
public void playVlc(String filename) {
System.out.println
("Playing vlc file: " + filename);
}
public void playMp4(String filename) {
// Do nothing
}
}
class Mp4Player implements AdvancedMediaPlayer {
public void playVlc(String filename) {
// Do nothing
}
public void playMp4(String filename) {
System.out.println("Playing mp4 file: " + filename);
}
}
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer mediaPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
mediaPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
mediaPlayer = new Mp4Player();
}
}
public void play(String audioType, String filename) {
if (audioType.equalsIgnoreCase("vlc")) {
mediaPlayer.playVlc(filename);
} else if (audioType.equalsIgnoreCase("mp4")) {
mediaPlayer.playMp4(filename);
}
}
}
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
public void play(String audioType, String filename) {
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file: " + filename);
} else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, filename);
} else {
System.out.println("Invalid media type: " + audioType);
}
}
}
public class AdapterPatternExample {
public static void main(String[] args) {
MediaPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "song.mp3");
audioPlayer.play("vlc", "video.vlc");
audioPlayer.play("mp4", "video.mp4");
// Output:
// Playing mp3 file: song.mp3
// Playing vlc file: video.vlc
// Playing mp4 file: video.mp4
}
}
```
Explanation: In this example, the `MediaPlayer` interface defines the `play` method. The `AdvancedMediaPlayer` interface provides specific methods for playing different types of media. The `VlcPlayer` and `Mp4Player` classes implement the `AdvancedMediaPlayer` interface. The `MediaAdapter` class implements the `MediaPlayer` interface and acts as an adapter between the `MediaPlayer` and `AdvancedMediaPlayer`. The `AudioPlayer` class implements the `MediaPlayer` interface and uses the `MediaAdapter` to play different types of media.
4. Decorator Pattern:
The Decorator pattern allows behavior to be added to an individual object dynamically, without affecting the behavior of other objects from the same class. It provides a flexible alternative to subclassing for extending functionality.
```java
interface Pizza {
String getDescription();
double getCost();
}
class MargheritaPizza implements Pizza {
public String getDescription() {
return "Margherita Pizza";
}
public double getCost() {
return 8.99;
}
}
abstract class PizzaDecorator implements Pizza {
protected Pizza pizza;
public PizzaDecorator(Pizza pizza) {
this.pizza = pizza;
}
public String getDescription() {
return pizza.getDescription();
}
public double getCost() {
return pizza.getCost();
}
}
class CheeseDecorator extends PizzaDecorator {
public CheeseDecorator(Pizza pizza) {
super(pizza);
}
public String getDescription() {
return pizza.getDescription() + ", extra cheese";
}
public double getCost() {
return pizza.getCost() + 2.50;
}
}
public class DecoratorPatternExample {
public static void main(String[] args) {
Pizza margherita = new MargheritaPizza();
Pizza margheritaWithCheese = new CheeseDecorator(margherita);
System.out.println(margherita.getDescription() + " - Cost: $" + margherita.getCost());
// Output: Margherita Pizza - Cost: $8.99
System.out.println(margheritaWithCheese.getDescription() + " - Cost: $" + margheritaWithCheese.getCost());
// Output: Margherita Pizza, extra cheese - Cost: $11.49
}
}
```
Explanation: In this example, the `Pizza` interface defines the basic operations of a pizza. The `MargheritaPizza` class implements the `Pizza` interface, representing a concrete pizza. The `PizzaDecorator` is an abstract class that implements the `Pizza` interface and holds a reference to a `Pizza` object. The `CheeseDecorator` extends the `PizzaDecorator` and adds additional behavior (extra cheese) to the pizza. By using the decorator pattern, we can dynamically add new toppings to a pizza without modifying the original pizza class.
5. Strategy Pattern:
The Strategy pattern defines a family of interchangeable algorithms and encapsulates each one. It allows the algorithms to be selected at runtime and promotes flexibility and extensibility.
```java
interface SortingStrategy {
void sort(int[] arr);
}
class BubbleSort implements SortingStrategy {
public void sort(int[] arr) {
// Bubble sort implementation
}
}
class MergeSort implements SortingStrategy {
public void sort(int[] arr) {
// Merge sort implementation
}
}
class SortingContext {
private SortingStrategy strategy;
public SortingContext(SortingStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(SortingStrategy strategy) {
this.strategy = strategy;
}
public void sortArray(int[] arr) {
strategy.sort(arr);
}
}
public class StrategyPatternExample {
public static void main(String[] args) {
int[] arr = {5, 2, 8, 1, 9};
SortingContext context = new SortingContext(new BubbleSort());
context.sortArray(arr);
// Perform bubble sort
context.setStrategy(new MergeSort());
context.sortArray(arr);
// Perform merge sort
}
}
```
Explanation: In this example, the `SortingStrategy` interface defines the sort operation.
The `BubbleSort` and `MergeSort` classes implement the `SortingStrategy` interface and provide different implementations of the sort algorithm. The `SortingContext` class encapsulates the selected sorting strategy and provides a method to perform the sorting operation. By changing the strategy at runtime, we can switch between different sorting algorithms without modifying the client code.
6. Builder Pattern:
The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. It simplifies object creation and improves readability.
```java
class Car {
private String brand;
private String model;
private int year;
private String color;
public Car(String brand, String model, int year, String color) {
this.brand = brand;
this.model = model;
this.year = year;
this.color = color;
}
// Getters and setters
}
class CarBuilder {
private String brand;
private String model;
private int year;
private String color;
public CarBuilder setBrand(String brand) {
this.brand = brand;
return this;
}
public CarBuilder setModel(String model) {
this.model = model;
return this;
}
public CarBuilder setYear(int year) {
this.year = year;
return this;
}
public CarBuilder setColor(String color) {
this.color = color;
return this;
}
public Car build() {
return new Car(brand, model, year, color);
}
}
public class BuilderPatternExample {
public static void main(String[] args) {
Car car = new CarBuilder()
.setBrand("Toyota")
.setModel("Camry")
.setYear(2022)
.setColor("Blue")
.build();
// Use the constructed car object
}
}
```
Explanation: In this example, the `Car` class represents a complex object that requires multiple attributes to be set during construction. The `CarBuilder` class provides methods to set each attribute and returns itself to enable method chaining. The `build` method creates a `Car` object with the provided attributes. This allows for a more readable and flexible way to construct objects, especially when there are many optional attributes.
7. MVC Pattern:
The MVC (Model-View-Controller) pattern separates the presentation, logic, and data components of an application. It enhances maintainability, reusability, and testability of the codebase.
```java
class Model {
private String data;
public void setData(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
class View {
public void displayData(String data) {
System.out.println("Data: " + data);
}
}
class Controller {
private Model model;
private View view;
public Controller(Model model, View view) {
this.model = model;
this.view = view;
}
public void updateData(String data) {
model.setData(data);
}
public void displayData() {
String data = model.getData();
view.displayData(data);
}
}
public class MVCPatternExample {
public static void main(String[] args) {
Model model = new Model();
View view = new View();
Controller controller = new Controller(model, view);
controller.updateData("Hello, MVC!");
controller.displayData();
// Output: Data: Hello, MVC!
}
}
```
Explanation: In this example, the `Model` represents the data and its associated operations. The `View` is responsible for rendering the data to the user. The `Controller` acts as an intermediary between the `Model` and `View`, handling user inputs and updating the `Model` accordingly. The MVC pattern allows for separation of concerns, making the code more modular and maintainable.

Comments
Post a Comment