ЛАБ 02

Дизайн Паттернууд

3.1 Лабораторийн зорилго

Энэ лабораторид та 4 үндсэн дизайн паттерныг Java дээр бие даан хэрэгжүүлнэ:

  1. Singleton
  2. Factory Method
  3. Observer
  4. Strategy

Шаардлагатай зүйлс: Лекц 01-д суулгасан Java JDK 17+ ба Eclipse IDE

3.2 Лаб 1: Singleton паттерн — Тохиргооны менежер

Даалгавар: Програмын тохиргоог удирдах ConfigManager класс бичих. Зөвхөн нэг объект байх ёстой.

Алхам 1: Eclipse-д шинэ төсөл үүсгэнэ (File → New → Java Project): DesignPatternsLab

Алхам 2: ConfigManager.java файл үүсгэнэ:

import java.util.HashMap;
import java.util.Map;

public class ConfigManager {
    // === Singleton хэрэгжүүлэлт ===

    // 1. Ганцхан instance хадгалах static талбар
    private static ConfigManager instance;

    // 2. Тохиргооны утгуудыг хадгалах map
    private Map<String, String> settings;

    // 3. Private constructor — гаднаас new хийж чадахгүй
    private ConfigManager() {
        settings = new HashMap<>();
        // Анхдагч тохиргоо
        settings.put("language", "mn");           // Хэл: Монгол
        settings.put("theme", "dark");            // Загвар: Харанхуй
        settings.put("fontSize", "14");           // Фонт хэмжээ: 14
        System.out.println("✅ ConfigManager үүслээ!");
    }

    // 4. Ганцхан instance-д хандах арга
    public static ConfigManager getInstance() {
        if (instance == null) {                   // Анх удаа дуудахад л үүсгэнэ
            instance = new ConfigManager();
        }
        return instance;
    }

    // 5. Тохиргоо авах
    public String get(String key) {
        return settings.getOrDefault(key, "тодорхойгүй");
    }

    // 6. Тохиргоо тохируулах
    public void set(String key, String value) {
        settings.put(key, value);
        System.out.println("⚙️ Тохиргоо өөрчлөгдлөө: " + key + " = " + value);
    }

    // 7. Бүх тохиргоог харуулах
    public void showAll() {
        System.out.println("\n📋 Бүх тохиргоо:");
        for (Map.Entry<String, String> entry : settings.entrySet()) {
            System.out.println("  " + entry.getKey() + " = " + entry.getValue());
        }
    }
}

Алхам 3: SingletonTest.java файлд тестлэх:

public class SingletonTest {
    public static void main(String[] args) {
        // Анхны instance авах
        ConfigManager config1 = ConfigManager.getInstance();
        config1.showAll();

        // Тохиргоо өөрчлөх
        config1.set("theme", "light");
        config1.set("fontSize", "16");

        // Хоёр дахь instance авах — ижил объект байх ёстой!
        ConfigManager config2 = ConfigManager.getInstance();
        config2.showAll();

        // Шалгах: хоёулаа ижил объект уу?
        System.out.println("\nconfig1 == config2: " + (config1 == config2));
        // → true (яг нэг объект!)

        // ConfigManager cm = new ConfigManager(); // ← АЛДАА! private constructor
    }
}

Хүлээгдэх үр дүн:

✅ ConfigManager үүслээ!
📋 Бүх тохиргоо:
  language = mn
  theme = dark
  fontSize = 14
⚙️ Тохиргоо өөрчлөгдлөө: theme = light
⚙️ Тохиргоо өөрчлөгдлөө: fontSize = 16
📋 Бүх тохиргоо:
  language = mn
  theme = light
  fontSize = 16
config1 == config2: true

3.3 Лаб 2: Factory Method паттерн — Дүрс үүсгэгч

Даалгавар: Өөр өөр дүрс (Shape) үүсгэдэг Factory бичих.

// ===== Shape интерфейс =====
interface Shape {
    void draw();           // Дүрс зурах
    double getArea();      // Талбай тооцоолох
}

// ===== Бодит дүрсүүд =====
class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("⭕ Тойрог зурлаа (радиус: " + radius + ")");
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;      // S = πr²
    }
}

class Rectangle implements Shape {
    private double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        System.out.println("⬜ Тэгш өнцөгт зурлаа (" + width + " x " + height + ")");
    }

    @Override
    public double getArea() {
        return width * height;                 // S = w × h
    }
}

class Triangle implements Shape {
    private double base, height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public void draw() {
        System.out.println("🔺 Гурвалжин зурлаа (суурь: " + base + ", өндөр: " + height + ")");
    }

    @Override
    public double getArea() {
        return 0.5 * base * height;            // S = ½ × b × h
    }
}

// ===== Factory =====
class ShapeFactory {
    public static Shape create(String type, double... params) {
        switch (type.toLowerCase()) {
            case "circle":
                return new Circle(params[0]);
            case "rectangle":
                return new Rectangle(params[0], params[1]);
            case "triangle":
                return new Triangle(params[0], params[1]);
            default:
                throw new IllegalArgumentException("Тодорхойгүй дүрс: " + type);
        }
    }
}

// ===== Тест =====
class FactoryTest {
    public static void main(String[] args) {
        // Factory-аар дүрсүүд үүсгэх
        Shape circle = ShapeFactory.create("circle", 5);
        Shape rect = ShapeFactory.create("rectangle", 4, 6);
        Shape tri = ShapeFactory.create("triangle", 3, 8);

        // Бүгдийг зурж, талбайг тооцоолох
        Shape[] shapes = {circle, rect, tri};
        for (Shape s : shapes) {
            s.draw();
            System.out.println("   Талбай: " + String.format("%.2f", s.getArea()));
        }
    }
}

3.4 Лаб 3: Observer паттерн — Цаг агаарын мэдээ

Даалгавар: Цаг агаарын станц өгөгдөл өөрчлөгдөхөд бүх дэлгэцүүдэд мэдэгдэх систем бичих.

import java.util.ArrayList;
import java.util.List;

// ===== Observer интерфейс =====
interface WeatherDisplay {
    void update(double temperature, double humidity);
}

// ===== Subject — Цаг агаарын станц =====
class WeatherStation {
    private List<WeatherDisplay> displays = new ArrayList<>();
    private double temperature;
    private double humidity;

    // Дэлгэц бүртгэх
    public void addDisplay(WeatherDisplay display) {
        displays.add(display);
    }

    // Дэлгэц хасах
    public void removeDisplay(WeatherDisplay display) {
        displays.remove(display);
    }

    // Өгөгдөл шинэчлэгдэхэд бүх дэлгэцэд мэдэгдэх
    public void setMeasurements(double temperature, double humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        System.out.println("\n🌡️ Шинэ хэмжилт: " + temperature + "°C, " + humidity + "% чийгшил");
        notifyDisplays();
    }

    private void notifyDisplays() {
        for (WeatherDisplay display : displays) {
            display.update(temperature, humidity);
        }
    }
}

// ===== Бодит дэлгэцүүд =====
class PhoneDisplay implements WeatherDisplay {
    private String ownerName;

    public PhoneDisplay(String ownerName) { this.ownerName = ownerName; }

    @Override
    public void update(double temperature, double humidity) {
        System.out.println("📱 " + ownerName + "-ийн утас: " + temperature + "°C, " + humidity + "%");
    }
}

class TVDisplay implements WeatherDisplay {
    private String channel;

    public TVDisplay(String channel) { this.channel = channel; }

    @Override
    public void update(double temperature, double humidity) {
        String feeling = temperature < 0 ? "❄️ Хүйтэн" : temperature < 20 ? "🌤️ Сэрүүн" : "☀️ Дулаан";
        System.out.println("📺 " + channel + ": " + feeling + " " + temperature + "°C");
    }
}

// ===== Тест =====
class ObserverTest {
    public static void main(String[] args) {
        // Станц үүсгэх
        WeatherStation station = new WeatherStation();

        // Дэлгэцүүд бүртгэх
        PhoneDisplay bat = new PhoneDisplay("Бат");
        PhoneDisplay bold = new PhoneDisplay("Болд");
        TVDisplay mnb = new TVDisplay("MNB");

        station.addDisplay(bat);
        station.addDisplay(bold);
        station.addDisplay(mnb);

        // Хэмжилт шинэчлэх → Бүгдэд мэдэгдэнэ
        station.setMeasurements(25.5, 60);
        station.setMeasurements(-15.0, 80);

        // Болдын дэлгэцийг хасах
        station.removeDisplay(bold);
        station.setMeasurements(10.0, 45);
    }
}

3.5 Лаб 4: Strategy паттерн — Хөнгөлөлтийн систем

Даалгавар: Дэлгүүрийн хөнгөлөлтийн олон стратеги (хувиар, тогтмол дүнгээр, шинэ жилийн) хэрэгжүүлэх.

// ===== Strategy интерфейс =====
interface DiscountStrategy {
    double applyDiscount(double originalPrice);
    String getDescription();
}

// ===== Бодит стратегиуд =====
class PercentageDiscount implements DiscountStrategy {
    private double percent;

    public PercentageDiscount(double percent) { this.percent = percent; }

    @Override
    public double applyDiscount(double originalPrice) {
        return originalPrice * (1 - percent / 100);
    }

    @Override
    public String getDescription() {
        return percent + "% хөнгөлөлт";
    }
}

class FixedDiscount implements DiscountStrategy {
    private double amount;

    public FixedDiscount(double amount) { this.amount = amount; }

    @Override
    public double applyDiscount(double originalPrice) {
        return Math.max(0, originalPrice - amount);     // 0-ээс бага болохгүй
    }

    @Override
    public String getDescription() {
        return amount + "₮ хөнгөлөлт";
    }
}

class NewYearDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double originalPrice) {
        if (originalPrice > 100000) {
            return originalPrice * 0.7;    // 100,000₮-аас дээш → 30% хөнгөлөлт
        }
        return originalPrice * 0.9;        // Бусад → 10% хөнгөлөлт
    }

    @Override
    public String getDescription() {
        return "🎄 Шинэ жилийн хөнгөлөлт (100K+ → 30%, бусад → 10%)";
    }
}

// ===== Context — Дэлгүүр =====
class Shop {
    private DiscountStrategy discountStrategy;

    public void setDiscountStrategy(DiscountStrategy strategy) {
        this.discountStrategy = strategy;
        System.out.println("🏷️ Стратеги: " + strategy.getDescription());
    }

    public void checkout(String item, double price) {
        double finalPrice = discountStrategy.applyDiscount(price);
        System.out.println("  " + item + ": " + price + "₮ → "
            + String.format("%.0f", finalPrice) + "₮"
            + " (хэмнэлт: " + String.format("%.0f", price - finalPrice) + "₮)");
    }
}

// ===== Тест =====
class StrategyTest {
    public static void main(String[] args) {
        Shop shop = new Shop();

        // Стратеги 1: 20% хөнгөлөлт
        shop.setDiscountStrategy(new PercentageDiscount(20));
        shop.checkout("Өвлийн курт", 150000);

        // Стратеги 2: 5000₮ хөнгөлөлт
        shop.setDiscountStrategy(new FixedDiscount(5000));
        shop.checkout("Номхон", 25000);

        // Стратеги 3: Шинэ жилийн хөнгөлөлт
        shop.setDiscountStrategy(new NewYearDiscount());
        shop.checkout("Зурагт", 500000);
        shop.checkout("Чихэвч", 30000);
    }
}