Creational patterns provide ways to create objects while hiding the creation logic. They decouple the object creation from the rest of the code, making the code more flexible and maintainable. Some of the well - known creational patterns are Singleton, Factory Method, and Abstract Factory.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// Product interface
interface Shape {
void draw();
}
// Concrete products
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}
// Creator abstract class
abstract class ShapeFactory {
public abstract Shape createShape();
}
// Concrete creator
class CircleFactory extends ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
class SquareFactory extends ShapeFactory {
@Override
public Shape createShape() {
return new Square();
}
}
Structural patterns are used to compose classes or objects into larger structures. They focus on how classes and objects are combined to form more complex structures. Some common structural patterns are Adapter, Decorator, and Proxy.
// Target interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee
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 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 Target
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);
}
}
}
// Component interface
interface Beverage {
String getDescription();
double cost();
}
// Concrete component
class Espresso implements Beverage {
@Override
public String getDescription() {
return "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
// Decorator abstract class
abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
}
// Concrete decorator
class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
@Override
public double cost() {
return beverage.cost() + 0.3;
}
}
Creational and structural patterns are essential tools in a Java developer’s toolkit. Creational patterns help in creating objects in a flexible and efficient way, while structural patterns assist in composing classes and objects into larger structures. By understanding the fundamental concepts, usage methods, common practices, and best practices of these patterns, developers can write more modular, maintainable, and flexible code.
When using these patterns, it is important to choose the right pattern for the right situation. Also, follow the best practices to ensure that the code is robust and performs well.