Java Generics টাইপ সেফ কোড লেখার পাশাপাশি ডিজাইন প্যাটার্নগুলির আরও ফ্লেক্সিবল এবং পুনঃব্যবহারযোগ্য বাস্তবায়নে গুরুত্বপূর্ণ ভূমিকা পালন করে। জেনেরিক্সের মাধ্যমে, আমরা ডিজাইন প্যাটার্নগুলিকে সাধারণত একাধিক ডেটা টাইপের জন্য ব্যবহার করতে পারি, যা কোড পুনরায় ব্যবহারযোগ্যতা এবং কার্যকারিতা বাড়ায়।
Generics ব্যবহার করে জনপ্রিয় Design Patterns
1. Factory Pattern
Factory Pattern এর মাধ্যমে অবজেক্ট তৈরি করার প্রক্রিয়া সহজ এবং ফ্লেক্সিবল করা যায়। জেনেরিক্স ব্যবহার করে, আমরা বিভিন্ন টাইপের অবজেক্ট তৈরির জন্য একটি সাধারণ ফ্যাক্টরি ক্লাস তৈরি করতে পারি।
উদাহরণ:
public class Factory<T> {
private Class<T> type;
public Factory(Class<T> type) {
this.type = type;
}
public T createInstance() throws InstantiationException, IllegalAccessException {
return type.newInstance();
}
public static void main(String[] args) {
try {
Factory<StringBuilder> stringBuilderFactory = new Factory<>(StringBuilder.class);
StringBuilder sb = stringBuilderFactory.createInstance();
sb.append("Hello, Generics!");
System.out.println(sb);
} catch (Exception e) {
e.printStackTrace();
}
}
}
বৈশিষ্ট্য:
একটি ফ্যাক্টরি ক্লাস একই প্যাটার্নে একাধিক টাইপের অবজেক্ট তৈরি করতে পারে।
2. Singleton Pattern
Singleton Pattern দিয়ে আমরা নিশ্চিত করতে পারি যে একটি ক্লাসের কেবল একটি ইনস্ট্যান্স বিদ্যমান থাকবে। Generics এর সাহায্যে, একই প্যাটার্ন একাধিক টাইপের জন্য ব্যবহার করা যায়।
উদাহরণ:
public class Singleton<T> {
private static Singleton<?> instance;
private T data;
private Singleton() {
}
@SuppressWarnings("unchecked")
public static <T> Singleton<T> getInstance() {
if (instance == null) {
instance = new Singleton<>();
}
return (Singleton<T>) instance;
}
public void setData(T data) {
this.data = data;
}
public T getData() {
return data;
}
public static void main(String[] args) {
Singleton<String> stringInstance = Singleton.getInstance();
stringInstance.setData("Hello, Singleton!");
System.out.println(stringInstance.getData());
Singleton<Integer> integerInstance = Singleton.getInstance();
integerInstance.setData(123); // Overwrites previous data
System.out.println(integerInstance.getData());
}
}
সীমাবদ্ধতা:
Generics এর Type Erasure এর কারণে, একই ক্লাসের জন্য শুধুমাত্র একটি ইনস্ট্যান্স রাখা সম্ভব।
3. Builder Pattern
Builder Pattern জটিল অবজেক্ট তৈরি করার প্রক্রিয়া সহজ করে। জেনেরিক্স ব্যবহার করে, বিল্ডার প্যাটার্নের কাস্টমাইজেশন আরও কার্যকর হয়।
উদাহরণ:
public class Builder<T> {
private T instance;
public Builder(T instance) {
this.instance = instance;
}
public T build() {
return instance;
}
public static void main(String[] args) {
Builder<StringBuilder> builder = new Builder<>(new StringBuilder());
StringBuilder sb = builder.build();
sb.append("Using Builder with Generics!");
System.out.println(sb);
}
}
ব্যবহার:
বিল্ডার প্যাটার্নে বিভিন্ন টাইপের অবজেক্ট সহজেই তৈরি করা যায়।
4. Observer Pattern
Observer Pattern ব্যবহার করে একাধিক অবজার্ভারকে একটি বিষয় (subject) এর পরিবর্তন সম্পর্কে অবগত করা হয়। Generics ব্যবহার করলে, অবজার্ভার এবং বিষয় উভয়ই টাইপ সেফ হয়।
উদাহরণ:
import java.util.ArrayList;
import java.util.List;
interface Observer<T> {
void update(T data);
}
class Subject<T> {
private List<Observer<T>> observers = new ArrayList<>();
public void addObserver(Observer<T> observer) {
observers.add(observer);
}
public void notifyObservers(T data) {
for (Observer<T> observer : observers) {
observer.update(data);
}
}
}
public class Main {
public static void main(String[] args) {
Subject<String> subject = new Subject<>();
subject.addObserver(data -> System.out.println("Observer 1: " + data));
subject.addObserver(data -> System.out.println("Observer 2: " + data));
subject.notifyObservers("Event Triggered!");
}
}
ব্যবহার:
এই প্যাটার্নে টাইপ সেফ অবজার্ভার তৈরি করা সহজ।
5. Decorator Pattern
Decorator Pattern ব্যবহার করে নতুন বৈশিষ্ট্য যোগ করে একটি অবজেক্টকে সাজানো হয়। জেনেরিক্স ব্যবহার করলে বিভিন্ন টাইপের জন্য পুনরায় ব্যবহারযোগ্য ডেকোরেটর তৈরি করা যায়।
উদাহরণ:
interface Component<T> {
T operation();
}
class ConcreteComponent implements Component<String> {
@Override
public String operation() {
return "Hello";
}
}
class Decorator<T> implements Component<T> {
private Component<T> component;
public Decorator(Component<T> component) {
this.component = component;
}
@Override
public T operation() {
System.out.println("Adding behavior...");
return component.operation();
}
}
public class Main {
public static void main(String[] args) {
Component<String> component = new ConcreteComponent();
Component<String> decoratedComponent = new Decorator<>(component);
System.out.println(decoratedComponent.operation());
}
}
ব্যবহার:
ডেকোরেটর প্যাটার্ন বিভিন্ন ধরনের অবজেক্ট সাজানোর জন্য উপযোগী।
6. Strategy Pattern
Strategy Pattern বিভিন্ন অ্যালগরিদম বা অপারেশনের জন্য আলাদা কৌশল (strategy) ব্যবহার করে। জেনেরিক্স ব্যবহার করে টাইপ-সেফ কৌশল তৈরি করা যায়।
উদাহরণ:
interface Strategy<T> {
T execute(T data);
}
class UpperCaseStrategy implements Strategy<String> {
@Override
public String execute(String data) {
return data.toUpperCase();
}
}
class IncrementStrategy implements Strategy<Integer> {
@Override
public Integer execute(Integer data) {
return data + 1;
}
}
public class Main {
public static void main(String[] args) {
Strategy<String> stringStrategy = new UpperCaseStrategy();
System.out.println(stringStrategy.execute("hello"));
Strategy<Integer> intStrategy = new IncrementStrategy();
System.out.println(intStrategy.execute(5));
}
}
ব্যবহার:
এই প্যাটার্ন জেনেরিক্সের সাহায্যে টাইপ-সেফ এবং পুনরায় ব্যবহারযোগ্য কৌশল তৈরি করে।
Generics ডিজাইন প্যাটার্নগুলির শক্তি ও ফ্লেক্সিবিলিটি বাড়ায়:
- টাইপ সেফটি নিশ্চিত করে।
- পুনরায় ব্যবহারযোগ্যতা বৃদ্ধি করে।
- কোড কমপ্লেক্সিটি হ্রাস করে।
- সাধারণ প্যাটার্নকে একাধিক টাইপের জন্য কার্যকর করে।
Generics এর সাথে ডিজাইন প্যাটার্ন ব্যবহার করলে সফটওয়্যার আর্কিটেকচার আরও কার্যকর ও মজবুত হয়।
Factory Pattern একটি জনপ্রিয় Creational Design Pattern, যা নির্দিষ্ট টাইপের অবজেক্ট তৈরি করার জন্য ব্যবহৃত হয়। Generics এর সাথে Factory Pattern ব্যবহার করলে কোড আরও ডাইনামিক, টাইপ-সেফ এবং পুনঃব্যবহারযোগ্য হয়।
Factory Pattern: সংক্ষিপ্ত ব্যাখ্যা
Factory Pattern এর মূল ধারণা হলো অবজেক্ট তৈরির কাজ সরাসরি ক্লায়েন্ট ক্লাসে না রেখে, একটি ফ্যাক্টরি ক্লাসের মাধ্যমে পরিচালনা করা। এটি কোডের Encapsulation বৃদ্ধি করে এবং Object Creation Logic কে মডুলার করে তোলে।
Generics এবং Factory Pattern এর সমন্বয়
1. Generic Factory Pattern এর উদাহরণ
// Generic Factory Class
public class Factory<T> {
private Class<T> type;
public Factory(Class<T> type) {
this.type = type;
}
public T createInstance() {
try {
return type.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error creating instance of: " + type.getName(), e);
}
}
}
// Example Usage
class Product {
public void display() {
System.out.println("Product created!");
}
}
public class Main {
public static void main(String[] args) {
Factory<Product> productFactory = new Factory<>(Product.class);
Product product = productFactory.createInstance();
product.display(); // Output: Product created!
}
}
বৈশিষ্ট্য:
- Generic Parameter
T: ফ্যাক্টরি ক্লাসটি যেকোনো টাইপের অবজেক্ট তৈরি করতে পারে। - Runtime Flexibility:
Class<T>প্যারামিটার ব্যবহার করে ডাইনামিক অবজেক্ট তৈরি করা যায়। - Type Safety: কম্পাইল টাইমে টাইপ চেকিং নিশ্চিত করে।
2. Factory Pattern এর সাথে Bounded Generics
যদি কোনো নির্দিষ্ট টাইপ বা টাইপের সীমা নির্ধারণ করতে হয়, তাহলে Bounded Generics ব্যবহার করা যেতে পারে।
// Bounded Generic Factory Class
public class BoundedFactory<T extends Number> {
private Class<T> type;
public BoundedFactory(Class<T> type) {
this.type = type;
}
public T createInstance() {
try {
return type.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error creating instance of: " + type.getName(), e);
}
}
}
// Example Usage
public class Main {
public static void main(String[] args) {
BoundedFactory<Integer> integerFactory = new BoundedFactory<>(Integer.class);
// Integer instance তৈরি করা সম্ভব নয় কারণ Integer এর No-Arg Constructor নেই।
}
}
বৈশিষ্ট্য:
T extends Number: টাইপটি শুধুমাত্রNumberবা তার সাবক্লাস হতে পারে।- এটি টাইপ ইনস্ট্যান্স তৈরি করতে নির্ধারিত টাইপের মধ্যেই সীমাবদ্ধ।
3. Factory Pattern এর মাধ্যমে Collections তৈরি
Generics এর সাথে Factory Pattern ব্যবহার করে ডাইনামিকভাবে Collections তৈরি করা যায়।
import java.util.ArrayList;
import java.util.List;
public class CollectionFactory<T> {
public List<T> createList() {
return new ArrayList<>();
}
}
// Example Usage
public class Main {
public static void main(String[] args) {
CollectionFactory<String> stringListFactory = new CollectionFactory<>();
List<String> stringList = stringListFactory.createList();
stringList.add("Hello");
stringList.add("Generics");
System.out.println(stringList); // Output: [Hello, Generics]
}
}
4. Factory Pattern এর মাধ্যমে Singleton Object তৈরি
Generics এর সাথে Factory Pattern ব্যবহার করে Singleton Object তৈরি করা সম্ভব।
// Generic Singleton Factory
public class SingletonFactory<T> {
private T instance;
private Class<T> type;
public SingletonFactory(Class<T> type) {
this.type = type;
}
public T getInstance() {
if (instance == null) {
try {
instance = type.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error creating instance of: " + type.getName(), e);
}
}
return instance;
}
}
// Example Usage
class Config {
public void display() {
System.out.println("Config loaded!");
}
}
public class Main {
public static void main(String[] args) {
SingletonFactory<Config> configFactory = new SingletonFactory<>(Config.class);
Config config1 = configFactory.getInstance();
Config config2 = configFactory.getInstance();
config1.display(); // Output: Config loaded!
// Both instances are the same
System.out.println(config1 == config2); // Output: true
}
}
5. Type Parameterized Factory Method
Factory Pattern এর সাথে জেনেরিক মেথড ব্যবহার করলে কোড আরও সংক্ষিপ্ত এবং সহজবোধ্য হয়।
public class FactoryUtil {
public static <T> T createInstance(Class<T> type) {
try {
return type.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error creating instance of: " + type.getName(), e);
}
}
}
// Example Usage
class User {
public void display() {
System.out.println("User created!");
}
}
public class Main {
public static void main(String[] args) {
User user = FactoryUtil.createInstance(User.class);
user.display(); // Output: User created!
}
}
Factory Pattern এবং Generics এর সুবিধা
- Code Reusability: Generics ব্যবহার করে ফ্যাক্টরি ক্লাস বা মেথড বিভিন্ন টাইপের জন্য পুনঃব্যবহারযোগ্য।
- Type Safety: কম্পাইল টাইমে টাইপ চেকিং নিশ্চিত করে, রানটাইম ত্রুটির ঝুঁকি কমায়।
- Flexibility: যেকোনো টাইপের অবজেক্ট ডাইনামিকভাবে তৈরি করা যায়।
- Encapsulation: অবজেক্ট তৈরির লজিক ক্লায়েন্ট কোড থেকে আলাদা করা যায়।
Factory Pattern এবং Generics ব্যবহার করার সময় সতর্কতা
- Type Erasure এর সীমাবদ্ধতা:
- Generics টাইপের তথ্য রানটাইমে হারিয়ে যায়। ফলে কিছু সময়ে ক্লাস টাইপ (
Class<T>) প্যারামিটার পাস করতে হয়।
- Generics টাইপের তথ্য রানটাইমে হারিয়ে যায়। ফলে কিছু সময়ে ক্লাস টাইপ (
- Reflection এর ওভারহেড:
- Generics এর মাধ্যমে অবজেক্ট তৈরিতে Reflection ব্যবহৃত হলে পারফরম্যান্স কমতে পারে।
- No-Arg Constructor প্রয়োজন:
- Factory Pattern ব্যবহার করার জন্য ক্লাসে ডিফল্ট (No-Argument) কন্সট্রাক্টর থাকা আবশ্যক।
Generics এর সাথে Factory Pattern ব্যবহার করলে কোড ডাইনামিক, টাইপ-সেফ এবং পুনঃব্যবহারযোগ্য হয়। এটি অবজেক্ট তৈরির প্রক্রিয়া সহজ করে এবং জাভার প্রোগ্রামিং আরও কার্যকর করে তোলে। সঠিক নকশায় এই সমন্বয় ব্যবহার করলে পারফরম্যান্স বাড়ানোর পাশাপাশি জটিলতাও হ্রাস পায়।
Singleton Pattern ডিজাইন প্যাটার্নে একটি ক্লাস থেকে শুধুমাত্র একটি ইনস্ট্যান্স তৈরি করা হয় এবং এটি গ্লোবাল অ্যাক্সেস প্রদান করে। Generics এর মাধ্যমে Singleton Pattern কে আরও ডাইনামিক, টাইপ-সেইফ এবং পুনঃব্যবহারযোগ্য করা যায়।
Singleton Pattern এবং Generics Integration এর সুবিধা
- টাইপ-সেইফ Singleton: Generics ব্যবহার করে নির্দিষ্ট টাইপের Singleton তৈরি করা যায়।
- Code Reusability: একই Singleton লজিক বিভিন্ন টাইপের জন্য পুনঃব্যবহার করা যায়।
- Dynamic Singleton Management: বিভিন্ন টাইপের জন্য একাধিক Singleton ইনস্ট্যান্স তৈরি ও ব্যবস্থাপনা করা যায়।
উদাহরণ ১: Simple Generic Singleton
public class GenericSingleton<T> {
private static GenericSingleton<?> instance;
private T value;
private GenericSingleton(T value) {
this.value = value;
}
@SuppressWarnings("unchecked")
public static <T> GenericSingleton<T> getInstance(T value) {
if (instance == null) {
instance = new GenericSingleton<>(value);
}
return (GenericSingleton<T>) instance;
}
public T getValue() {
return value;
}
}
ব্যবহার:
public class Main {
public static void main(String[] args) {
GenericSingleton<String> stringSingleton = GenericSingleton.getInstance("Hello");
System.out.println("String Value: " + stringSingleton.getValue());
GenericSingleton<Integer> intSingleton = GenericSingleton.getInstance(100);
System.out.println("Integer Value: " + intSingleton.getValue()); // একই ইনস্ট্যান্স রিটার্ন করবে
}
}
আউটপুট:
String Value: Hello
Integer Value: Hello
বিঃদ্রঃ: এখানে একাধিক টাইপের Singleton এর জন্য একই ইনস্ট্যান্স ব্যবহৃত হয়।
উদাহরণ ২: Type-Safe Singleton for Each Type
প্রতিটি টাইপের জন্য আলাদা Singleton তৈরি করতে হলে, একটি ConcurrentHashMap ব্যবহার করা যেতে পারে।
import java.util.concurrent.ConcurrentHashMap;
public class TypeSafeSingleton<T> {
private static final ConcurrentHashMap<Class<?>, Object> instances = new ConcurrentHashMap<>();
private T value;
private TypeSafeSingleton(T value) {
this.value = value;
}
@SuppressWarnings("unchecked")
public static <T> TypeSafeSingleton<T> getInstance(Class<T> clazz, T value) {
return (TypeSafeSingleton<T>) instances.computeIfAbsent(clazz, k -> new TypeSafeSingleton<>(value));
}
public T getValue() {
return value;
}
}
ব্যবহার:
public class Main {
public static void main(String[] args) {
TypeSafeSingleton<String> stringSingleton = TypeSafeSingleton.getInstance(String.class, "Hello Generics");
System.out.println("String Value: " + stringSingleton.getValue());
TypeSafeSingleton<Integer> intSingleton = TypeSafeSingleton.getInstance(Integer.class, 123);
System.out.println("Integer Value: " + intSingleton.getValue());
}
}
আউটপুট:
String Value: Hello Generics
Integer Value: 123
উদাহরণ ৩: Singleton Factory with Generics
Generics এর মাধ্যমে Singleton Factory তৈরি করা যায়, যেখানে বিভিন্ন ক্লাসের Singleton ইনস্ট্যান্স তৈরি এবং সংরক্ষণ করা যায়।
import java.util.HashMap;
import java.util.Map;
public class SingletonFactory {
private static final Map<Class<?>, Object> instanceMap = new HashMap<>();
@SuppressWarnings("unchecked")
public static <T> T getInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException {
synchronized (instanceMap) {
if (!instanceMap.containsKey(clazz)) {
instanceMap.put(clazz, clazz.newInstance());
}
return (T) instanceMap.get(clazz);
}
}
}
ব্যবহার:
public class Main {
public static void main(String[] args) throws Exception {
// Singleton for String class
String stringInstance = SingletonFactory.getInstance(String.class);
System.out.println("String Instance: " + stringInstance);
// Singleton for Integer class
Integer integerInstance = SingletonFactory.getInstance(Integer.class);
System.out.println("Integer Instance: " + integerInstance);
}
}
Singleton Pattern এর ক্ষেত্রে Generics এর সুবিধা
- Reusable Code: একবার জেনেরিক Singleton ক্লাস লিখলে এটি বিভিন্ন টাইপের জন্য ব্যবহার করা যায়।
- Type Safety: কম্পাইল টাইমে টাইপ ত্রুটি শনাক্ত করা যায়।
- Dynamic Type Handling: একই লজিক ব্যবহার করে একাধিক টাইপের Singleton তৈরি এবং সংরক্ষণ করা যায়।
- Concurrency Handling:
ConcurrentHashMapব্যবহার করে থ্রেড-সেইফ Singleton তৈরি করা সম্ভব।
Java Generics এবং Singleton Pattern এর সমন্বয় টাইপ সেফ, পুনঃব্যবহারযোগ্য এবং ডাইনামিক Singleton তৈরি করতে সহায়ক। Type Erasure এর কারণে, রানটাইম পারফরম্যান্সেও কোনো প্রভাব পড়ে না। ডেভেলপাররা Generics এর মাধ্যমে সহজেই বিভিন্ন টাইপের Singleton ব্যবহার এবং সংরক্ষণ করতে পারে।
Observer Pattern হল একটি আচরণগত নকশা (Behavioral Design Pattern), যা অবজেক্টের মধ্যে one-to-many সম্পর্ক প্রতিষ্ঠা করে। কোনো অবজেক্টের স্টেট পরিবর্তিত হলে সংশ্লিষ্ট অবজেক্টগুলোকে স্বয়ংক্রিয়ভাবে অবহিত করার জন্য এটি ব্যবহৃত হয়।
Generics ব্যবহার করে Observer Pattern আরও Type-Safe, Flexible, এবং Reusable করা যায়।
Observer Pattern এর গঠন:
- Subject (Observable): স্টেট ট্র্যাক করে এবং পর্যবেক্ষকদের (Observers) নোটিফাই করে।
- Observer: স্টেট পরিবর্তনের প্রতি সাড়া দেয়।
Generics সহ Observer Pattern এর উদাহরণ
1. Observer ইন্টারফেস
public interface Observer<T> {
void update(T data);
}
2. Subject ইন্টারফেস
import java.util.ArrayList;
import java.util.List;
public class Subject<T> {
private List<Observer<T>> observers = new ArrayList<>();
public void addObserver(Observer<T> observer) {
observers.add(observer);
}
public void removeObserver(Observer<T> observer) {
observers.remove(observer);
}
public void notifyObservers(T data) {
for (Observer<T> observer : observers) {
observer.update(data);
}
}
}
Generics সহ Observer Pattern ব্যবহার
স্টেপ ১: বাস্তবায়ন
public class ConcreteObserver implements Observer<String> {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String data) {
System.out.println(name + " received update: " + data);
}
}
স্টেপ ২: ব্যবহার
public class Main {
public static void main(String[] args) {
Subject<String> subject = new Subject<>();
Observer<String> observer1 = new ConcreteObserver("Observer 1");
Observer<String> observer2 = new ConcreteObserver("Observer 2");
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers("Hello, Observers!");
}
}
আউটপুট:
Observer 1 received update: Hello, Observers!
Observer 2 received update: Hello, Observers!
Generics ব্যবহার করার সুবিধা
- Type-Safety: টাইপ কাস্টিংয়ের প্রয়োজন নেই।
- Reusability: বিভিন্ন টাইপের জন্য একই Observer Pattern পুনঃব্যবহার করা যায়।
- Flexibility: একই Subject বিভিন্ন ধরনের Observers পরিচালনা করতে পারে।
Advanced Example: Multiple Observers with Different Data Types
Multi-Type Observer
public class MultiTypeObserver<T> implements Observer<T> {
private String name;
public MultiTypeObserver(String name) {
this.name = name;
}
@Override
public void update(T data) {
System.out.println(name + " received: " + data);
}
}
Multi-Type Subject ব্যবহার
public class Main {
public static void main(String[] args) {
Subject<Integer> intSubject = new Subject<>();
Subject<String> stringSubject = new Subject<>();
Observer<Integer> intObserver = new MultiTypeObserver<>("Integer Observer");
Observer<String> stringObserver = new MultiTypeObserver<>("String Observer");
intSubject.addObserver(intObserver);
stringSubject.addObserver(stringObserver);
intSubject.notifyObservers(100);
stringSubject.notifyObservers("Generics in Observer Pattern");
}
}
আউটপুট:
Integer Observer received: 100
String Observer received: Generics in Observer Pattern
Real-World Application: Stock Price Monitoring
Observer: Stock Price Listener
public class StockPriceObserver implements Observer<Double> {
private String investorName;
public StockPriceObserver(String investorName) {
this.investorName = investorName;
}
@Override
public void update(Double price) {
System.out.println(investorName + " notified: Stock price updated to $" + price);
}
}
Subject: Stock Price Tracker
public class StockPriceTracker extends Subject<Double> {
private double price;
public void setPrice(double price) {
this.price = price;
notifyObservers(price);
}
}
Main Program
public class Main {
public static void main(String[] args) {
StockPriceTracker tracker = new StockPriceTracker();
Observer<Double> investor1 = new StockPriceObserver("Investor 1");
Observer<Double> investor2 = new StockPriceObserver("Investor 2");
tracker.addObserver(investor1);
tracker.addObserver(investor2);
tracker.setPrice(150.50);
tracker.setPrice(160.75);
}
}
আউটপুট:
Investor 1 notified: Stock price updated to $150.5
Investor 2 notified: Stock price updated to $150.5
Investor 1 notified: Stock price updated to $160.75
Investor 2 notified: Stock price updated to $160.75
Generics এবং Observer Pattern একত্রে ব্যবহার করলে:
- টাইপ-সেফ এবং পুনঃব্যবহারযোগ্য কোড তৈরি হয়।
- বিভিন্ন ডেটা টাইপের Observers এবং Subjects সহজেই পরিচালনা করা যায়।
- বাস্তব জীবনের সমস্যাগুলোর (যেমন Stock Price Monitoring) জন্য উন্নত এবং কার্যকর সমাধান প্রদান করা যায়।
জেনেরিক্স ব্যবহারের মাধ্যমে জাভায় বিভিন্ন ডিজাইন প্যাটার্ন আরও নমনীয়, টাইপ সেফ, এবং পুনঃব্যবহারযোগ্য করা যায়। এতে কোড আরও পরিষ্কার এবং সহজবোধ্য হয়। এখানে জেনেরিক্স ব্যবহার করে কয়েকটি গুরুত্বপূর্ণ ডিজাইন প্যাটার্নের উদাহরণ দেওয়া হলো।
1. Singleton Design Pattern
Singleton প্যাটার্ন নিশ্চিত করে যে একটি ক্লাসের একটি মাত্র ইনস্ট্যান্স তৈরি হয়। Generics ব্যবহার করে Singleton ক্লাস তৈরি করলে এটি টাইপ সেফ এবং পুনঃব্যবহারযোগ্য হয়।
Singleton Generics Implementation:
public class Singleton<T> {
private static Singleton<?> instance;
private T value;
private Singleton(T value) {
this.value = value;
}
@SuppressWarnings("unchecked")
public static <T> Singleton<T> getInstance(T value) {
if (instance == null) {
instance = new Singleton<>(value);
}
return (Singleton<T>) instance;
}
public T getValue() {
return value;
}
}
// ব্যবহার:
public class Main {
public static void main(String[] args) {
Singleton<String> stringSingleton = Singleton.getInstance("Hello Generics!");
System.out.println(stringSingleton.getValue());
Singleton<Integer> intSingleton = Singleton.getInstance(42);
System.out.println(intSingleton.getValue()); // Same instance used
}
}
সুবিধা:
- টাইপ সেফটি নিশ্চিত।
- পুনঃব্যবহারযোগ্য Singleton ক্লাস।
2. Factory Design Pattern
Factory প্যাটার্ন ব্যবহার করে বিভিন্ন টাইপের অবজেক্ট তৈরি করা যায়। Generics ব্যবহার করলে এটি আরও নমনীয় হয় এবং টাইপ ইনফরমেশন ধরে রাখা যায়।
Factory Generics Implementation:
public class Factory<T> {
private Class<T> type;
public Factory(Class<T> type) {
this.type = type;
}
public T createInstance() throws IllegalAccessException, InstantiationException {
return type.newInstance();
}
}
// ব্যবহার:
public class Main {
public static void main(String[] args) {
try {
Factory<StringBuilder> factory = new Factory<>(StringBuilder.class);
StringBuilder sb = factory.createInstance();
sb.append("Hello, Factory Pattern!");
System.out.println(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
সুবিধা:
- একই Factory ক্লাস ব্যবহার করে বিভিন্ন টাইপের অবজেক্ট তৈরি করা যায়।
- টাইপ কাস্টিংয়ের প্রয়োজন নেই।
3. Strategy Design Pattern
Strategy প্যাটার্ন ব্যবহার করে ডাইনামিক্যালি আলাদা আলাদা অ্যালগরিদম সিলেক্ট করা যায়। Generics এর মাধ্যমে Strategy প্যাটার্ন টাইপ সেফ এবং পুনঃব্যবহারযোগ্য করা যায়।
Strategy Generics Implementation:
public interface Strategy<T> {
void execute(T data);
}
public class PrintStrategy implements Strategy<String> {
@Override
public void execute(String data) {
System.out.println("Printing: " + data);
}
}
public class SquareStrategy implements Strategy<Integer> {
@Override
public void execute(Integer data) {
System.out.println("Square: " + (data * data));
}
}
// ব্যবহার:
public class Main {
public static void main(String[] args) {
Strategy<String> printStrategy = new PrintStrategy();
printStrategy.execute("Generics with Strategy!");
Strategy<Integer> squareStrategy = new SquareStrategy();
squareStrategy.execute(5);
}
}
সুবিধা:
- টাইপ সুনির্দিষ্টভাবে বিভিন্ন স্ট্রাটেজি হ্যান্ডল করা যায়।
- সহজে নতুন স্ট্রাটেজি যোগ করা যায়।
4. Observer Design Pattern
Observer প্যাটার্নে একাধিক অবজার্ভার অবজেক্টকে একটি সাবজেক্টে সংযুক্ত করা হয়। Generics ব্যবহার করে বিভিন্ন টাইপের অবজার্ভার সহজে পরিচালনা করা যায়।
Observer Generics Implementation:
import java.util.ArrayList;
import java.util.List;
public class Subject<T> {
private List<Observer<T>> observers = new ArrayList<>();
public void addObserver(Observer<T> observer) {
observers.add(observer);
}
public void notifyObservers(T data) {
for (Observer<T> observer : observers) {
observer.update(data);
}
}
}
public interface Observer<T> {
void update(T data);
}
// ব্যবহার:
public class StringObserver implements Observer<String> {
@Override
public void update(String data) {
System.out.println("String Observer: " + data);
}
}
public class IntegerObserver implements Observer<Integer> {
@Override
public void update(Integer data) {
System.out.println("Integer Observer: " + data);
}
}
public class Main {
public static void main(String[] args) {
Subject<String> stringSubject = new Subject<>();
stringSubject.addObserver(new StringObserver());
stringSubject.notifyObservers("Hello Observers!");
Subject<Integer> integerSubject = new Subject<>();
integerSubject.addObserver(new IntegerObserver());
integerSubject.notifyObservers(42);
}
}
সুবিধা:
- একাধিক টাইপের অবজার্ভার ব্যবস্থাপনা সহজ।
- টাইপ কাস্টিংয়ের প্রয়োজন নেই।
5. Builder Design Pattern
Builder প্যাটার্ন ব্যবহার করে জটিল অবজেক্ট ধাপে ধাপে তৈরি করা হয়। Generics ব্যবহার করে এটি আরও ফ্লেক্সিবল করা যায়।
Builder Generics Implementation:
public class Builder<T> {
private T instance;
public Builder(T instance) {
this.instance = instance;
}
public T build() {
return instance;
}
public Builder<T> withProperty(String property) {
System.out.println("Setting property: " + property);
return this;
}
}
// ব্যবহার:
public class Main {
public static void main(String[] args) {
Builder<StringBuilder> builder = new Builder<>(new StringBuilder());
StringBuilder sb = builder.withProperty("Custom Property").build();
sb.append("Hello, Builder Pattern!");
System.out.println(sb.toString());
}
}
সুবিধা:
- বিভিন্ন টাইপের অবজেক্ট ধাপে ধাপে তৈরি করা যায়।
- টাইপ সেফটি নিশ্চিত।
Generics ডিজাইন প্যাটার্ন বাস্তবায়নে টাইপ সেফটি, পুনঃব্যবহারযোগ্যতা, এবং ফ্লেক্সিবিলিটি নিশ্চিত করে। জাভার বিভিন্ন ডিজাইন প্যাটার্ন যেমন Singleton, Factory, Strategy, Observer, এবং Builder-এ Generics ব্যবহারে টাইপ সম্পর্কিত ত্রুটি এড়ানো যায় এবং কোড আরও পরিষ্কার ও কার্যকর হয়।
Read more