Java and Kotlin Design Patterns with Code Examples
Design patterns are reusable solutions to common problems in software design, helping developers build scalable, maintainable, and flexible…
Java and Kotlin Design Patterns with Code Examples
Design patterns are reusable solutions to common problems in software design, helping developers build scalable, maintainable, and flexible code. Here’s an exploration of five popular design patterns implemented in both Java and Kotlin, showcasing each pattern’s unique benefits.
1. Singleton Pattern
Definition
The Singleton Pattern restricts a class to only one instance, providing global access to that instance. It’s ideal for cases where one instance is needed across the entire application.
Java Implementation
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Kotlin Implementation
Kotlin’s object keyword simplifies Singleton creation, providing thread-safety by default.
object Singleton {
fun showMessage() = println("Singleton instance")
}
2. Factory Pattern
Definition
The Factory Pattern defines a way to create objects while hiding the creation logic. This pattern is useful when creating multiple related classes through a common interface.
Java Implementation
abstract class Animal {
public abstract String sound();
}
class Dog extends Animal {
@Override
public String sound() {
return "Woof";
}
}
class Cat extends Animal {
@Override
public String sound() {
return "Meow";
}
}
class AnimalFactory {
public static Animal createAnimal(String type) {
if ("Dog".equalsIgnoreCase(type)) return new Dog();
else if ("Cat".equalsIgnoreCase(type)) return new Cat();
return null;
}
}
// Usage
Animal dog = AnimalFactory.createAnimal("Dog");
System.out.println(dog.sound()); // Output: Woof
Kotlin Implementation
abstract class Animal {
abstract fun sound(): String
}
class Dog : Animal() {
override fun sound() = "Woof"
}
class Cat : Animal() {
override fun sound() = "Meow"
}
object AnimalFactory {
fun createAnimal(type: String): Animal? = when (type) {
"Dog" -> Dog()
"Cat" -> Cat()
else -> null
}
}
// Usage
val dog = AnimalFactory.createAnimal("Dog")
println(dog?.sound()) // Output: Woof
3. Observer Pattern
Definition
The Observer Pattern creates a one-to-many dependency between objects, where a subject notifies observers about any state changes. This is particularly useful for event-driven architectures.
Java Implementation
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received: " + message);
}
}
class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
// Usage
Subject subject = new Subject();
Observer observer1 = new ConcreteObserver("Observer1");
subject.addObserver(observer1);
subject.notifyObservers("Hello Observers!"); // Observer1 received: Hello Observers!
Kotlin Implementation
interface Observer {
fun update(message: String)
}
class ConcreteObserver(private val name: String) : Observer {
override fun update(message: String) {
println("$name received: $message")
}
}
class Subject {
private val observers = mutableListOf<Observer>()
fun addObserver(observer: Observer) {
observers.add(observer)
}
fun notifyObservers(message: String) {
observers.forEach { it.update(message) }
}
}
// Usage
val subject = Subject()
val observer1 = ConcreteObserver("Observer1")
subject.addObserver(observer1)
subject.notifyObservers("Hello Observers!") // Observer1 received: Hello Observers!
4. Strategy Pattern
Definition
The Strategy Pattern defines a family of algorithms, encapsulating each algorithm within a class and making them interchangeable. This pattern is beneficial when a system requires different behaviors at runtime.
Java Implementation
interface Strategy {
int doOperation(int num1, int num2);
}
class OperationAdd implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
class OperationMultiply implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
// Usage
Context context = new Context(new OperationAdd());
System.out.println(context.executeStrategy(5, 3)); // Output: 8
Kotlin Implementation
interface Strategy {
fun doOperation(num1: Int, num2: Int): Int
}
class OperationAdd : Strategy {
override fun doOperation(num1: Int, num2: Int) = num1 + num2
}
class OperationMultiply : Strategy {
override fun doOperation(num1: Int, num2: Int) = num1 * num2
}
class Context(private val strategy: Strategy) {
fun executeStrategy(num1: Int, num2: Int) = strategy.doOperation(num1, num2)
}
// Usage
val context = Context(OperationAdd())
println(context.executeStrategy(5, 3)) // Output: 8
5. Builder Pattern
Definition
The Builder Pattern separates object construction from its representation, allowing for complex objects to be created step-by-step. This is especially useful for objects with multiple configurations.
Java Implementation
class Computer {
private String CPU;
private String RAM;
private String storage;
private Computer(Builder builder) {
this.CPU = builder.CPU;
this.RAM = builder.RAM;
this.storage = builder.storage;
}
public static class Builder {
private String CPU;
private String RAM;
private String storage;
public Builder setCPU(String CPU) {
this.CPU = CPU;
return this;
}
public Builder setRAM(String RAM) {
this.RAM = RAM;
return this;
}
public Builder setStorage(String storage) {
this.storage = storage;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
// Usage
Computer computer = new Computer.Builder().setCPU("Intel").setRAM("16GB").setStorage("1TB").build();
Kotlin Implementation
class Computer private constructor(
val CPU: String?,
val RAM: String?,
val storage: String?
) {
data class Builder(
var CPU: String? = null,
var RAM: String? = null,
var storage: String? = null
) {
fun setCPU(CPU: String) = apply { this.CPU = CPU }
fun setRAM(RAM: String) = apply { this.RAM = RAM }
fun setStorage(storage: String) = apply { this.storage = storage }
fun build() = Computer(CPU, RAM, storage)
}
}
// Usage
val computer = Computer.Builder().setCPU("Intel").setRAM("16GB").setStorage("1TB").build()
Conclusion
Design patterns like Singleton, Factory, Observer, Strategy, and Builder play a crucial role in creating organized, reusable, and scalable software. Implementing these patterns in Java and Kotlin showcases the versatility and conciseness of each language. Understanding these patterns can empower developers to solve complex design challenges with ease and improve code readability, maintainability, and flexibility.
메타데이터
- post_id
- be66596d09bb
- slug
- java-and-kotlin-design-patterns-with-code-examples-be66596d09bb
- url
- https://medium.com/@YodgorbekKomilo/java-and-kotlin-design-patterns-with-code-examples-be66596d09bb
- canonical_url
- https://medium.com/@YodgorbekKomilo/java-and-kotlin-design-patterns-with-code-examples-be66596d09bb
- author_url
- https://medium.com/@YodgorbekKomilo
- status
- ok
- fetched_at
- 2026-07-24 14:14:02