Design patterns are general, reusable solutions to commonly occurring problems in software design. They are not a finished design that can be transformed directly into source or machine code. Instead, they are descriptions or templates for how to solve a problem that can be used in many different situations.
Design patterns are typically classified into three main categories:
Let’s take the Singleton pattern as an example. The Singleton pattern ensures that a class has only one instance and provides a global point of access to it.
public class Singleton {
private static Singleton instance;
private Singleton() {
// Private constructor to prevent instantiation from outside the class
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
In the above code, the constructor of the Singleton
class is private, which prevents other classes from creating instances of it directly. The getInstance
method is used to get the single instance of the class.
// Interface
interface Shape {
void draw();
}
// Concrete classes
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
// Factory class
class ShapeFactory {
public Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
}
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
}
return null;
}
}
You can use the factory like this:
public class FactoryExample {
public static void main(String[] args) {
ShapeFactory factory = new ShapeFactory();
Shape circle = factory.getShape("CIRCLE");
circle.draw();
}
}
// Target interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee class
class AdvancedMediaPlayer {
public void playVlc(String fileName) {
System.out.println("Playing vlc file: " + fileName);
}
public void playMp4(String fileName) {
System.out.println("Playing mp4 file: " + fileName);
}
}
// Adapter class
class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMediaPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMediaPlayer = new AdvancedMediaPlayer();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMediaPlayer.playVlc(fileName);
}
}
}
// Concrete implementation of MediaPlayer
class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file: " + fileName);
} else if (audioType.equalsIgnoreCase("vlc")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
}
}
}
import java.util.ArrayList;
import java.util.List;
// Subject interface
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// Observer interface
interface Observer {
void update();
}
// Concrete subject
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private int temperature;
public void setTemperature(int temperature) {
this.temperature = temperature;
notifyObservers();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
public int getTemperature() {
return temperature;
}
}
// Concrete observer
class TemperatureDisplay implements Observer {
private WeatherStation weatherStation;
public TemperatureDisplay(WeatherStation weatherStation) {
this.weatherStation = weatherStation;
weatherStation.registerObserver(this);
}
@Override
public void update() {
System.out.println("Temperature updated: " + weatherStation.getTemperature());
}
}
Java design patterns are powerful tools that can help developers create more robust, flexible, and maintainable applications. By understanding the fundamental concepts, usage methods, common practices, and best practices of design patterns, developers can make better design decisions and write high - quality code. However, it’s important to use design patterns judiciously and avoid over - engineering.