스프링의 유래

'스프링'이라는 이름의 유래는 이전에 Java EE(엔터프라이즈 에디션)의 스펙을 구현한 EJB가 있었습니다.
EJB 스펙을 따르는 오브젝트들은 객체지향적인 특징과 장점을 포기해야 했습니다.
EJB는 상속과 다형성 등의 혜택을 제대로 누릴 수 없었습니다.
그럼에도 EJB가 계속 사용되었던 이유는 엔터프라이즈 애플리케이션에서 반드시 필요로 하는 주요한 엔터프라이즈 서비스들을 애플리케이션 코드와 분리해서 독립적인 서비스로 사용할 수 있게 만들어졌다는 점입니다.
EJB가 기술의 복잡도가 증가해서 성능이 느렸던 것을 탈피하여, EJB 시절을 “겨울”에 빗대어 겨울 후의 “봄”으로 새로운 시작한다는 것을 의미하는 Spring(봄)이 되었습니다.

 

Spring이란 무엇인가?

Spring은 Framework입니다.
Framework를 그대로 번역하면 뼈대라는 뜻입니다.
컴퓨터 프로그래밍에서는 복잡한 문제를 해결하거나 서술하는 데 사용되는 기본 개념 구조입니다.
Spring Framework는 Java 애플리케이션 개발을 위한 포괄적인 인프라 지원을 제공하는 Java 플랫폼입니다. 
Spring이 인프라를 처리하므로 애플리케이션에 집중할 수 있습니다.
Spring을 사용하면 "plain old Java objects"(POJO)에서 애플리케이션을 빌드하고 POJO에 엔터프라이즈 서비스를 침해하지 않고 적용할 수 있습니다. 
이 기능은 Java SE 프로그래밍 모델과 전체 및 부분 Java EE에 적용됩니다.

 

프레임워크 모듈

Spring Framework는 약 20개의 모듈로 구성된 기능으로 구성되어 있습니다. 
이러한 모듈은 다음 다이어그램과 같이 그룹화됩니다.

Core Container
Data Access/Integration
Web, AOP(Aspect Oriented Programming)
Instrumentation
Messaging
Test

 

 

Spring 주요 특징

POJO(Plain Old Java Object) 방식

POJO는 Java EE의 EJB 를 사용하면서 해당 플랫폼에 종속되어 있는 무거운 객체들을 만드는 것에 반발하며 나타난 용어다. 별도의 프레임워크 없이 Java EE를 사용할 때에 비해 특정 인터페이스를 직접 구현하거나 상속받을 필요가 없어 기존 라이브러리를 지원하기가 용이하고, 객체가 가볍습니다.

 

관점 지향 프로그래밍(Aspect Oriented Programming, AOP)

컴퓨팅에서 관점 지향 프로그래밍(aspect-oriented programming, AOP)은 횡단 관심사(cross-cutting concern)의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임입니다.
코드 그 자체를 수정하지 않는 대신 기존의 코드에 추가 동작(어드바이스)을 추가함으로써 수행하며, "함수의 이름이 'set'으로 시작하면 모든 함수 호출을 기록한다"와 같이 어느 코드가 포인트 컷(pointcut) 사양을 통해 수정되는지를 따로 지정합니다.
이를 통해 기능의 코드 핵심부를 어수선하게 채우지 않고도 비즈니스 로직에 핵심적이지 않은 동작들을 프로그램에 추가할 수 있게 합니다.
관점 지향 프로그래밍은 관점 지향 소프트웨어 개발의 토대를 형성합니다.

AOP에 대해 더 알아보기

 

의존성 주입(Dependency Injection, DI)

프로그래밍에서 구성요소 간의 의존 관계가 소스코드 내부가 아닌 외부에서 설정을 통해 정의되는 방식입니다.
코드 재사용을 높여 소스코드를 다양한 곳에 사용할 수 있으며 모듈간의 결합도도 낮출 수 있습니다.
계층, 서비스 간에 의존성이 존재하는 경우 스프링 프레임워크가 서로 연결시켜줍니다.

 

제어 역전(Inversion of Control, IoC)

전통적인 프로그래밍에서는 개발자가 작성한 프로그램이 외부 라이브러리의 코드를 호출해서 이용했습니다.
제어 역전은 이와 반대로 외부 라이브러리 코드가 개발자의 코드를 호출하게 됩니다.
즉, 제어권이 프레임워크에게 있어 필요에 따라 스프링 프레임워크가 사용자의 코드를 호출합니다.

 

생명주기 관리

스프링 프레임워크는 Java 객체의 생성, 소멸을 직접 관리하며 필요한 객체만 사용할 수 있습니다.

 

후기

백엔드 개발자를 하게 된다면 스프링은 들어볼 수밖에 없습니다.
하지만 스프링을 사용하는 이유를 잘 모르는 사람도 많습니다.
이미 큰 규모의 인프라가 구축되어 있어서 스프링 시장이 잘 죽지 않고 다른 언어에 비해 조만간 사라질 언어는 아니다 라는 느낌으로 사용하는 분들도 있습니다.
틀린 말은 아닙니다.
하지만 스프링이 왜 탄생하게 되었고, 그래서 다른 프레임워크들과 비교했을 때 장점이 무엇인지 알게 된다면 스프링의 특징을 잘 활용할 수 있다고 생각합니다.

 

참조 자료: Spring IO 페이지

참조 자료: Spring 나무위키

 

디자인패턴 Command Pattern

커맨드 패턴은 요구사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수도 있습니다.

또한 요청 내역을 큐에 저장하거나 로그로 기록할 수도 있으며, 작업취소 기능도 지원 가능합니다.

 

커맨드 패턴은 실제로 어떻게 적용할까요?

커맨드 객체는 일련의 행동을 특정 리시버하고 연결시킴으로써 요구 사항을 캡슐화한 것입니다.

리시버를 한 객체에 넣어주고, 실행시키기 위한 메서드 하나만 외부에 공개하는 방법을 사용합니다.

이 메소드 호출에 의해 리시버에서 일련의 작업이 처리됩니다.

외부에서는 어떤 객체가 리시버 역할을 하는지 모르고 실제로 어떤 일을 하는지 알 수 없습니다.

실행을 위한 메서드만 호출하면 요구 사항이 처리된다는 것만 알 수 있습니다.

 

Command

package cg.park.designpattern.command;

public interface Command {
    public void execute();
}

 

 

Light ON/OFF

불을 켜고 끄는 Light ON과 Light OFF 라는 기능이 있습니다.

사용자 입장에서는 버튼만 누르면 기능은 실행됩니다.

사용자는 버튼을 눌렀을 때 내부적으로 어떤 일이 일어나는지 모릅니다.

이렇게 요청 내역을 객체로 캡슐화하고, 클라이언트는 버튼 하나로 기능을 제공받을 수 있습니다.

 

구현

Command

package cg.park.designpattern.command;

public interface Command {
    public void execute();
}

 

Light

package cg.park.designpattern.command;

public class Light {

    public Light() {}

    public void on() {System.out.println("Light: ON");}

    public void off() {System.out.println("Light: OFF");}

}

 

LightOff

package cg.park.designpattern.command;

public class LightOff implements Command {
    Light light;

    public LightOff(Light light) {
        this.light = light;
    }

    public void execute() {
        light.off();
    }
}

 

LightOn

package cg.park.designpattern.command;

public class LightOn implements Command {
    Light light;

    public LightOn(Light light) {
        this.light = light;
    }

    public void execute() {
        light.on();
    }
}

 

RemoteControl

package cg.park.designpattern.command;

public class RemoteControl {

    public static void main(String[] args) {
        Light light = new Light();
        LightOn lightOn = new LightOn(light);
        LightOff lightOff = new LightOff(light);

        lightOn.execute();//ON
        lightOff.execute();//OFF
    }

}

위의 main 클래스에서 execute를 사용하면 LightON, LightOFF가 실행됩니다.

 

후기

처음에는 Facade Pattern과 뭐가 다른지 궁금했었는데, 직접 자료를 찾아보고 구현해보면서 알게되었습니다.
감사합니다.

퍼사드 패턴 보러가기

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

디자인 패턴 Singleton Pattern 

 

싱글턴 패턴은 클래스 인스턴스가 하나만 만들어지도록 하고, 그 인스턴스에 대한 전역 접근을 제공합니다.

 

인스턴스 생성을 위해서 getInstance()를 호출해야 함.

 

싱글턴 패턴은 실제로 어떻게 적용할까요?

클래스에서 단 하나만 생성할 수 있게 관리합니다.

다른 클래스에서도 자신의 인스턴스를 추가로 만들지 못하도록 해야 합니다.

인스턴스를 생성하길 원하면 반드시 Singleton 클래스의 getInstance()를 호출해야 합니다.

 

인스턴스 접근을 어디에서도 가능하게 만들어야 합니다.

다른 객체에서 인스턴스가 필요할 때 언제든지 Singleton 클래스에 요청을 할 수 있게 만들고, 요청이 들어오면 getInstance()에서 만들어진 유일한 인스턴스를 반환해줘야 합니다.

 

Singleton Pattern 인스턴스를 이해하려면?

Singleton 인스턴스 사용

Singleton 인스턴스 사용을 유료 서비스 구독이라고 가정해봅니다.

유료 서비스를 이용할 때 구독이 안되어 있다면 최초 이용 시 구독 신청을 하게 됩니다.

구독 신청이 된 후 서비스를 제공받을 수 있습니다.

그리고 다시 서비스를 이용할 경우, 구독이 되어있다면 구독 신청 없이 바로 서비스를 제공받게 됩니다.

이미 구독 중이라면 다시 구독 신청을 할 필요는 없습니다.

 

 

Instance로 바꿔서 생각해보면?

위의 내용들을 Singleton Pattern의 인스턴스로 바꿔서 생각해보면 Singleton은 쉽게 느껴질 것입니다.

유료 서비스: Instance 호출

구독 중: Instance 존재 여부

구독 신청: Instance 생성

서비스 제공: Instance 제공

 

 

Singleton Class

package cg.park.designpattern.singleton;

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                    return singleton;
                }
            }
        }
        return singleton;
    }

}

 

Singleton 인스턴스를 호출 시 getInstance()를 호출해야 합니다.

객체가 Null 인지(생성된 적이 없는지) 확인을 합니다.

그리고 동시에 이 영역에 접근하지 못하게 synchronized를 선언합니다.

그리고 이 찰나에 null이 되었는지 체크를 다시 한 후에

Singleton 인스턴스를 생성 후 반환해줍니다.

Singleton 인스턴스가 이미 생성되었다면 바로 반환해줍니다.

 

synchronized를 제일 앞에 선언하지 않은 이유

synchronized가 있다면 이미 생성된 Singleton 인스턴스라도 모든 접근이 동기화되기 때문입니다.

동기화가 된다는 것은 인스턴스를 사용하기 위해 접근하려면 줄을 서야 하는 것입니다.

점심시간에 인기가 많은 식당을 가게 되면 줄을 서는 것처럼 Singleton 인스턴스를 사용하기 위해서는 줄을 서야 합니다.

하지만 이미 인스턴스가 있고 반환만 해주면 되는 상태라면 굳이 줄을 설 필요가 없습니다.

그래서 제일 위해 null 체크를 한 후, 인스턴스가 없는 경우에만 synchronized를 선언합니다.

synchronized가 선언되었기 때문에 1개의 인스턴스만 만들어지고, 그 후에는 이미 생성된 인스턴스를 반환할 것입니다.

 

구현

Paid라는 클래스를 만들어서 Singleton 인스턴스를 호출합니다.

Paid

package cg.park.designpattern.singleton;

public class Paid {

    public void perpare(String name) {
        System.out.println("유료 서비스 ========== START ==========");
        System.out.println(name +" 접속");
        Singleton.getInstance();
        System.out.println("유료 서비스 ========== E N D ==========");
    }
}

 

Singleton에 접근하면 로그를 남기도록 합니다.

Singleton

package cg.park.designpattern.singleton;

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    System.out.println("결제 후 서비스 제공");
                    singleton = new Singleton();
                    return singleton;
                }
            }
        }
        System.out.println("서비스 제공");
        return singleton;
    }

}

 

Paid를 호출할 Main 클래스를 만듭니다.

Main

package cg.park.designpattern.singleton;

public class Main {

    public static void main(String[] args) {
        Paid paid = new Paid();
        paid.perpare("1년 전");
        paid.perpare("반년 전");
        paid.perpare("현재");
    }

}

 

위의 로그를 보면 첫 번째 접근 시에만 결제 후 서비스를 제공하고 그다음부터는 바로 서비스를 제공합니다.

 

후기

Singleton Pattern은 꼭 필요한 경우가 아니라면 사용하지 않는 것을 추천합니다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

Factory Method Pattern

 

팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듭니다.

팩토리 메소드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡기는 것입니다.

 


 

설명

 

버거 주문 과정을 만드려고 합니다.

버거 주문을 받아서 준비 완료까지 만들 예정입니다.

Burger라는 추상 클래스를 만든 다음 Burger를 만들어줄 구상 클래스를 만듭니다.

 

public void orderBurger(String type) {
    Burger burger;
    if (type.equals("cheese")) {
        //burger 객체에 치즈버거 인스턴스 삽입
    }
    else if (type.equals("bulgogi")) {
        //burger 객체에 불고기버거 인스턴스 삽입
    }
    // 버거 종류만큼 else if ...
    
    burger.order();//주문
    burger.makeBurger();//준비 중
    burger.done();//준비 완료
}

 

type의 값에 따라서 인스턴스를 반환합니다.

저렇게 코딩을 하게 되면 메뉴가 늘어날 때마다 주문을 받는 기능에 메뉴를 계속 생성해줍니다.

그래서 인스턴스를 생성해주는 기능을 서브클래스로 분리합니다.

 

public Burger createBurger(String type) {
    if (type.equals("cheese")) return new CheeseBurger();
    if (type.equals("bulgogi")) return new BulgogiBurger();
    if (type.equals("chicken")) return new ChickenBurger();
    return null;
}

 

type에 따라서 인스턴스를 반환해주는 기능만 따로 분리한 것입니다.

이렇게 되면 위에 주문받는 기능은 간단해집니다.

 

 

type에 따라서 인스턴스 생성하던 부분을 서브클래스에 위임하여 코드가 간결해졌습니다.

 

 

Burger: 버거 만드는 과정 추상화.

CheeseBurger: 치즈버거 구현.

ChickenBurger: 치킨버거 구현.

BulgogiBurger: 불고기버거 구현.

BurgerFactory: 구현된 버거 클래스 호출.

BurgerStore: 주문받는 클래스.

Order: 주문하는 클래스.

 


 

구현

 

Burger

package cg.park.designpattern.factory.burgers;

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

abstract public class Burger {
    String name;
    String bun;
    String sauce;
    List<String> toppings = new ArrayList<>();

    public String getName() {
        return name;
    }

    public void order() {
        System.out.println("===========START===========");
        System.out.println("[주문]");
        System.out.println(name);
    }

    public void makeBurger() {
        System.out.println("[재료]");
        System.out.println(bun);
        System.out.println(sauce);
        toppings.forEach(str -> System.out.println(str));
    }

    public void done() {
        System.out.println("===========E N D===========");
    }

}

 

CheeseBurger

package cg.park.designpattern.factory.burgers;

public class CheeseBurger extends Burger {
    public CheeseBurger() {
        name = "Cheese Burger";
        bun = "Cheese bun";
        sauce = "Cheese sauce";
        toppings.add("Cheese");
        toppings.add("Patty");
    }
}

 

BurgerFactory

package cg.park.designpattern.factory.burgers;

public class BurgerFactory {

    public Burger createBurger(String type) {
        if (type.equals("cheese")) return new CheeseBurger();
        if (type.equals("bulgogi")) return new BulgogiBurger();
        if (type.equals("chicken")) return new ChickenBurger();
        return null;
    }
}

 

BurgerStore

package cg.park.designpattern.factory.burgers;

public class BurgerStore {
    BurgerFactory factory;

    public BurgerStore(BurgerFactory factory) {
        this.factory = factory;
    }

    public void orderBurger(String type) {
        Burger burger = factory.createBurger(type);
        burger.order();//주문
        burger.makeBurger();//준비 중
        burger.done();//준비 완료
    }

}

 

Order

package cg.park.designpattern.factory.burgers;

public class Order {

    public static void main(String[] args) {
        BurgerStore store = new BurgerStore(new BurgerFactory());
        store.orderBurger("cheese");//치즈버거 주문
        store.orderBurger("bulgogi");//불고기버거 주문
        store.orderBurger("chicken");//치킨버거 주문
    }

}

 


 

후기

 

팩토리 메서드 패턴에 대해 알아봤습니다.

객체를 생성하기 위한 인터페이스를 정의하고, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만드는 방법입니다. 

감사합니다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

Decorator Pattern

 

객체에 추가 요소를 동적으로 더할 수 있습니다.

데코레이터를 사용하면 서브클래스를 만드는 경우에 비해 훨씬 유연하게 기능을 확장할 수 있습니다.

 


 

설명

 

버거 가게 메뉴를 만드려고 합니다.

버거 단품을 주문할 수 있지만 그 단품에 패티, 치즈 등 재료를 추가할 수도 있습니다.

이렇게 추가 기능을 만들 때 각각의 버거 클래스에 패티 추가, 치즈 추가 등 옵션 기능을 추가해주는 것이 맞을까요?

 

Burger

package cg.park.designpattern.decorator.buger;

public class CheeseBurger extends Burger {
    public CheeseBurger() {
        description = "Basic Burger";
    }//버거 이름
    public double cost() {
        return 6000;
    }//버거 가격
}

 

치즈버거 객체입니다.

현재 구현된 것은 버거 이름과 버거 가격입니다.

옵션 추가 기능을 클래스에 부여한다고 가정해보겠습니다.

 

Burger

package cg.park.designpattern.decorator.buger;

public class CheeseBurger extends Burger {
    public CheeseBurger() {
        description = "Basic Burger";
    }//버거 이름
    public double cost() {
        return 6000;
    }//버거 가격

    //------추가 부분------
    public void addCheese() {};//치즈 추가
    public void addPatty() {};//패티 추가
}

 

치즈 추가와 패티 추가내용을 하단에 만들었습니다.

예를 들어 100개의 버거 클래스가 있다면 모든 클래스에 추가 내용을 만들어줘야 합니다.

그리고 추가하는 옵션이 달라질 때마다 100개의 버거 클래스를 수정해야합니다.

과연 이것이 유지보수에 용이하게 만들어진 것일까요?

이번 Decorator Pattern에서는 옵션의 기능을 따로 분리하여 만들 것입니다.

만들어진 객체 코드를 수정하지 않고 추가 기능을 더할 수 있는 패턴입니다.

 

BurgerOption

package cg.park.designpattern.decorator.bugger;

public abstract class BurgerOption extends Burgger {
    public Burgger burgger;
    public abstract String getDescription();

    public Burgger.Size getSize() {return burgger.getSize();}
}

 

버거의 옵션을 담당할 클래스를 따로 만들어줍니다.

 

 

전체 버거의 구성을 잡아줄 추상 클래스 Burger를 만듭니다.

Burger를 상속받아서 각 버거 클래스를 구현합니다.

BurgerOption 클래스도 만들어줍니다.

BurgerOption 클래스는 각 옵션들의 부모 클래스가 될 것이기 때문에 추상 클래스로 구현합니다.

BurgerOption을 상속받은 각 옵션 클래스들을 구현합니다.

 

구현

 

Burger

package cg.park.designpattern.decorator.bugger;

public abstract class Burger {
    public enum Size { BASIC, LARGE };//버거 사이즈
    Burger.Size size = Size.BASIC;//기본 제공 사이즈
    String description = "Unknown Burger";//버거 설명

    //버거 설명
    public String getDescription() {
        return description;
    }

    //버거 사이즈 변경
    public void setSize(Burger.Size size) {
        this.size = size;
    }

    //버거 사이즈 조회
    public Size getSize() {
        return this.size;
    }

    //버거 가격
    public abstract double cost();

}

 

BasicBurger

package cg.park.designpattern.decorator.bugger;

public class BasicBurger extends Burger {
    
    //버거 설명: 기본 버거
    public BasicBurger() {
        description = "Basic Burger";
    }

    //가격: 5000
    public double cost() {
        return 5000;
    }
}

버거 클래스에 사이즈 변경 및 옵션 기능을 넣지 않았습니다.

버거 외 부가적인 기능은 옵션 클래스로 분리하였습니다.

 

BurgerOption

package cg.park.designpattern.decorator.bugger;

public abstract class BurgerOption extends Burger {
    public Burger burger;//버거 객체 상속
    public abstract String getDescription();//버거 설명

    //버거 사이즈
    public Size getSize() {return burger.getSize();}
}

 

Cheese

package cg.park.designpattern.decorator.bugger;

public class Cheese extends BurgerOption {
    //버거 객체
    public Cheese(Burger burger) {
        this.burger = burger;
    }

    //버거 설명에 Cheese 추가
    public String getDescription() {return burger.getDescription() + ", Cheese";}

    //버거 가격에 Cheese 가격 추가
    public double cost() {return burger.cost() + 500;}
}

 

PcgBurger

package cg.park.designpattern.decorator.bugger;

public class PcgBurger {

    public static void main(String args[]) {
        Burger burger = new BasicBurger();//기본 버거
        System.out.println("[첫 번째 주문] " +
                "\n버거: "+ burger.getDescription()
                +"\n가격: "+ burger.cost()
        );

    }
}

버거 주문 시 결과입니다.

기본 버거뿐 아니라 치즈버거, 치킨버거, 불고기버거와 옵션도 추가해보겠습니다.

 

package cg.park.designpattern.decorator.bugger;

import cg.park.designpattern.decorator.bugger.Burger.Size;

public class PcgBurger {

    public static void main(String args[]) {
        Burger burger = new BasicBurger();//기본 버거
        System.out.println("[첫 번째 주문] " +
                "\n버거: "+ burger.getDescription()
                +"\n가격: "+ burger.cost()
        );

        Burger cheeseBurger = new CheeseBurger();//치즈 버거
        cheeseBurger = new Cheese(cheeseBurger);//치즈 추가
        System.out.println("[두 번째 주문] " +
                "\n버거: "+cheeseBurger.getDescription()
                +"\n가격: "+cheeseBurger.cost()
        );

        Burger bulgogiBurger = new BulgogiBurger();//불고기 버거
        bulgogiBurger.setSize(Size.LARGE);//라지로 변경
        System.out.println("[세 번째 주문] " +
                "\n버거: "+bulgogiBurger.getDescription()
                +"\n가격: "+bulgogiBurger.cost()
        );

        Burger chickenBurger = new ChickenBurger();//치킨 버거
        chickenBurger = new Chicken(chickenBurger);//치킨 추가
        System.out.println("[네 번째 주문] " +
                "\n버거: "+chickenBurger.getDescription()
                +"\n가격: "+chickenBurger.cost()
        );
    }
}

각 주문의 결과를 확인할 수 있습니다.

 


 

후기

 

실무에서 클래스에 기능을 추가하는 일은 많습니다.

기능을 추가할 때 단순하게 생각하면 해당 클래스나 메서드 밑에 기능을 추가하면 됩니다.

하지만 이렇게 만든다면 전체 프로젝트 중 방금 수정한 1개의 클래스를 위해 작업이 진행된 것입니다.

해당 추가 기능에 대해 분석, 설계가 잘 되었다면 다행이지만, 임시 방안으로 만든 내용이라면 추후에 문제가 발생할 수 있습니다.

이런 상황에서 프로젝트에 대한 이해와 기본기가 탄탄하면 보다 좋은 방향으로 기능을 추가할 수 있습니다.

감사합니다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

Observer Pattern

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의합니다.

 


 

설명

 

4남매가 식자재를 판매 창업을 하려고 합니다.

4남매가 각각 창고에 재고관리를 하지 않고, 1개의 창고에 모든 재고를 공유하기로 했습니다.

첫째: 정육점

둘째: 과일가게

셋째: 야채가게

넷째: 종합마트

이렇게 각각 다른 창업을 시작했습니다.

4남매 모두 하나의 창고를 이용해 관리하고, 재고가 변동될 때마다 창고에 의존하고 있는 각 매장에 변동 내용을 알려주고 있습니다.

 

 

정육점에고 고기가 판매된다면 남아있는 재고를 각 매장에 알려주고 고기를 팔아야 할 다른 매장에서도 재고 현황을 확인하고 판매할 수 있습니다.

이렇게 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의하는 것이 "옵저버 패턴"입니다.

 

옵저버 패턴은 모든 옵저버 객체를 관리하고 알림을 대신해줍니다.

N : N 관계가 아닌 1 : N입니다.

각 객체마다 엮여있는 결합도 높은 코드가 아닌 1 : N 관계로 결합도가 낮고 응집성은 높아집니다.

얼마든지 옵저버 객체를 추가할 수 있고, 삭제할 수도 있습니다.

설계된 코드를 변경할 필요도 없습니다.

그리고 각 옵저버 객체의 코드가 바뀌어도 서로에게 영향을 미치지 않습니다.

 


 

구현

 

객체 상태가 바뀌면 동작할 인터페이스를 정의합니다.

바뀐 상태를 갱신시켜주는 update와 그 내역을 보여주는 display를 만듭니다.

 

Observer

package cg.park.designpattern.observer;

public interface Observer {
   public void update(int meat, int fruit, int vegetable);
}

 

DisplayElement

package cg.park.designpattern.observer;

public interface DisplayElement {
   public void display();
}

 

재고의 현재 상태를 알림 받는 기능을 인터페이스로 만듭니다.

생성, 삭제, 알림 


Subject

package cg.park.designpattern.observer;

public interface Subject {
   public void registerObserver(Observer o);
   public void removeObserver(Observer o);
   public void notifyObservers();
}

 

Subject를 상속받아서 재고의 현재 상태를 알림 받는 클래스를 구현합니다.

 

PreorderData

package cg.park.designpattern.observer;

import java.util.*;

public class PreorderData implements Subject {
    private List<Observer> observers;

    private int meat;//고기
    private int fruit;//과일
    private int vegetable;//야채

    public PreorderData() {
        observers = new ArrayList<Observer>();
    }

    public void registerObserver(Observer o) {
        observers.add(o);
    }

    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(meat, fruit, vegetable);
        }
    }

    public void foodMaterialsChange(){
        notifyObservers();
    }

    public void setFoodMaterials(int meat, int fruit, int vegetable){
        this.meat = meat;
        this.fruit = fruit;
        this.vegetable = vegetable;
        foodMaterialsChange();
    }

}

 

그리고 처음에 만든 Observer와 DisplayElement를 상속받아 클래스를 구현합니다.

 

MeatShop

package cg.park.designpattern.observer;

public class MeatShop implements Observer, DisplayElement {

    private int meat;//과일
    private PreorderData preorderData;

    public MeatShop(PreorderData preorderData){
        this.preorderData = preorderData;
        preorderData.registerObserver(this);
    }

    public void update(int meat, int fruit, int vegetable) {
        this.meat = meat;
        display();
    }

    public void display() {
        System.out.println("[정육점] 고기재고: "+meat);
    }
}

 

FruitShop

package cg.park.designpattern.observer;

public class FruitShop implements Observer, DisplayElement {

    private int fruit;//과일
    private PreorderData preorderData;

    public FruitShop(PreorderData preorderData){
        this.preorderData = preorderData;
        preorderData.registerObserver(this);
    }

    public void update(int meat, int fruit, int vegetable) {
        this.fruit = fruit;
        display();
    }

    public void display() {
        System.out.println("[과일가게] 과일재고: "+fruit);
    }

}

 

VegetableShop

package cg.park.designpattern.observer;

public class VegetableShop implements Observer, DisplayElement {

    private int meat;//과일
    private PreorderData preorderData;

    public VegetableShop(PreorderData preorderData){
        this.preorderData = preorderData;
        preorderData.registerObserver(this);
    }

    public void update(int meat, int fruit, int vegetable) {
        this.meat = meat;
        display();
    }

    public void display() {
        System.out.println("[야채가게] 야채재고: "+meat);
    }
}

 

FoodShop

package cg.park.designpattern.observer;

public class FoodShop implements Observer, DisplayElement {

    private int meat;//고기
    private int fruit;//과일
    private int vegetable;//야채
    private PreorderData preorderData;

    public FoodShop(PreorderData preorderData){
        this.preorderData = preorderData;
        preorderData.registerObserver(this);
    }

    public void update(int meat, int fruit, int vegetable) {
        this.meat = meat;
        this.fruit = fruit;
        this.vegetable = vegetable;
        display();
    }

    public void display() {
        System.out.print("[종합마트] 고기재고: "+meat);
        System.out.print(" 과일재고: "+fruit);
        System.out.println(" 야채재고: "+vegetable);
    }
}

 

마지막으로 각 기능을 테스트해봅니다.

 

PreOrder

package cg.park.designpattern.observer;

public class PreOrder {

    public static void main(String[] args) {
        PreorderData preorderData = new PreorderData();

        MeatShop meatShop           = new MeatShop(preorderData);
        FruitShop fruitShop         = new FruitShop(preorderData);
        VegetableShop vegetableShop = new VegetableShop(preorderData);
        FoodShop foodShop           = new FoodShop(preorderData);

        preorderData.setFoodMaterials(10, 20, 30);//오픈 시 재고
        preorderData.setFoodMaterials(5, 20, 30);//고기 5개 판매

        preorderData.removeObserver(vegetableShop);//야채가게는 별도로 창고를 구입하여 이제 연락받지 않음.

        preorderData.setFoodMaterials(5, 10, 30);//과일 10개 판매
    }
}

 

창고의 재고가 달라진 만큼 각 가게에 재고가 갱신되는 것을 확인할 수 있습니다.

 


 

후기

 

옵저버 패턴을 직접 알아보고 구현하는 것을 추천합니다.

모든 디자인 패턴을 모든 코드에 녹일 수는 없겠지만, 기본적으로 알고 있어야 필요할 때 효율적인 디자인 패턴을 사용할 수 있습니다.

감사합니다.

 

Git : https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

Strategy Pattern

알고리즘 군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.

스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.

 


 

구현

 

의자를 만들 때 컴퓨터로 미리 디자인을 하려고 합니다.

의자는 휠이 있는 의자도 있고 휠이 없는 의자도 있습니다.

그리고 의자의 종류는 다양합니다.

사무용 의자, 흔들의자, 무중력 의자 등...

이런 의자를 객체지향 기법을 사용하여 Chair라는 부모 클래스를 만든 다음, 그 클래스를 확장하여 다른 종류의 의자들도 만듭니다.

public abstract class Chair {
    Moves moves;
    Sounds sounds;
}

 

의자는 휠이 있어서 움직이는 의자인지 아니면 휠이 없어서 움직이지 못하는 의자인지 정의하고,

의자의 종류마다 어떤 소리가 나는지 만들 예정입니다.

 

Moves

package cg.park.designpatten.designpatten.strategy;

public interface Moves {
    public void move();
}

 

Sounds

package cg.park.designpatten.designpatten.strategy;

public interface Sounds {
    public void sound();
}

 

의자가 움직이는지와 어떤 소리가 나는지에 대해서 정의합니다.

하지만 움직이지 않은 의자의 경우 소리가 나지 않습니다.

그러면 어떤 방법으로 클래스를 설계하고 구현해야 할까요?

 

 

이렇게 N:N으로 클래스마다 이것저것 만들어 놓는다면 같은 의자라도 어떤 방식으로 구현되었는지 감이 오지 않을 것입니다.

 

*디자인 원칙

애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.

 

코드에 새로운 요구사항이 있을 때마다 바뀌는 부분이 있다면, 그 행동을 바뀌지 않는 다른 부분으로부터 골라내서 분리해야 한다는 것을 알 수 있습니다.

 

 

바뀌는 부분과 그렇지 않은 부분을 분리하여 설계를 합니다.

*Chair

의자를 추상화해놓고 의자의 종류마다 특성에 맞게 구현하였습니다.

*Moves 

움직임에 대해 의자 종류에 상관없이 휠이 있다/없다 로 분리하였습니다.

*Sounds

소리에 대해서는 의자 종류마다 다른 소리가 나는 것을 표현하였습니다.

 

*디자인 원칙

구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.

 

"인터페이스에 맞춰서 프로그래밍한다"는 것은 "상위 형식에 맞춰서 프로그래밍한다"는 것을 뜻합니다.

의자 종류마다 소리를 알고 싶을 때 구성에 맞춰서 프로그래밍을 한다면 다음과 같이 할 수 있습니다.

//무중력의자 소리
ZeroGravityChair zeroGravityChair = new ZeroGravityChair();
zeroGravityChair.ZeroGravityChairSound();

//사무용의자 소리
OfficeChair officeChair = new OfficeChair();
officeChair.OfficeChairSound();

//흔들의자 소리
RockingChair rockingChair = new RockingChair();
rockingChair.RockingChairSound();

 

하지만 인터페이스/상위 형식에 맞춰서 프로그래밍한다면 다음과 같이 할 수 있습니다.

//무중력의자 소리
Chair zeroGravityChair = new ZeroGravityChair();
zeroGravityChair.eventSounds();

//사무용의자 소리
Chair officeChair = new OfficeChair();
officeChair.eventSounds();

//흔들의자 소리
Chair rockingChair = new RockingChair();
rockingChair.eventSounds();

 

객체 생성이 소리 메서드를 호출할 때 룰이 생깁니다.

 

*동적으로 소리를 지정하는 방법

 

package cg.park.designpatten.designpatten.strategy;

public abstract class Chair {
    Moves moves;
    Sounds sounds;

    public Chair (){}

    public void eventMoves() { moves.move(); }

    public void eventSounds() { sounds.sound(); }

    public void setMoves(Moves moves) {
        this.moves = moves;
    }

    public void setSounds(Sounds sounds) {
        this.sounds = sounds;
    }

}

 

Chair 클래스에 Moves와 Sounds를 동적으로 바꿀 수 있게 setMoves와 setSounds를 생성합니다.

 

//무중력의자 인스턴스 생성
Chair chair = new ZeroGravityChair();
chair.eventMoves();//움직임 여부 호출
chair.eventSounds();//의자 소리 호출

chair.setMoves(new dontMovingChair());//못움직이는 의자로 선언
chair.setSounds(new RockingChairSound());//흔들의자 소리로 변경
chair.eventMoves();//움직임 여부 호출
chair.eventSounds();//의자 소리 호출

 

각 단계에 맞게 움직임과 소리가 호출되고 있습니다.

 

"A에는 B가 있다" 관계에 대해 생각해 봅시다.

Moves와 Sounds가 있으며, 각각 움직임과 소리를 위임받습니다.

두 클래스를 이런 방법으로 합치는 것을 구성을 이용하는 것이라고 부릅니다.

위에 나와있는 의자 클래스에서 행동을 상속받는 대신, 올바른 행동 객체로 구성됨으로써 행동을 부여받게 됩니다.

이 기술은 매우 중요한 기술입니다.

사실 우리가 사용한 이 방법은 세 번째 디자인 원칙입니다.

 

*디자인 원칙

상속보다는 구성을 활용한다.

 

이 구성을 이용하여 시스템을 만들면 유연성을 크게 향상할 수 있습니다.

단순히 알고리즘 군을 별도의 클래스의 집합으로 캡슐화할 수 있도록 만들어주는 것뿐 아니라, 구성요소로 사용하는 객체에서 올바른 행동 인터페이스를 구현하기만 하면 실행 시에 행동을 바꿀 수도 있게 해 줍니다.

 

후기

 

디자인 패턴이라는 말이 처음에는 어렵게 느껴졌다.

하지만 개발 생활을 해보면서 느낀 점은 이러한 방법론은 선배 개발자님들의 시행착오를 바탕으로 더 나은 개발 방법인 것이다.

단어를 암기하듯 디자인 패턴을 암기하는 것이 아니라 직접 구현해보면서 이 패턴이 왜 생겨났고, 그래서 뭐가 더 좋아졌는지를 알게 된다면 더 이상 암기는 하지 않아도 될 것이다.

이번에 구현해본 Strategy Pattern은 실무에서도 많이 볼 수 있는 패턴이다.

의자나 오리가 아닌 각 서비스에 맞는 구현 주제에 관련하여 인터페이스에 맞게 프로그래밍을 하고, 알고리즘들을 캡슐화하여 교환할 수 있게 만드는 방법은 알아둘 필요가 있다. 

감사합니다.

 

Git : https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

LogIn 의미

 

컴퓨터 보안에서 로그인(login: 등록가입)과 로그아웃(logout)은 접근 허가 증명을 얻기 위해 사용자 인증으로 개인이 컴퓨터 시스템에 접근하는 작업을 말한다. 컴퓨터 보안에서 중요한 역할을 담당한다. 사용자 자격 증명은 일반적으로 사용자 이름과 그에 일치하는 비밀번호 형태를 이루며 이를 기반으로 사용자는 접근을 얻기 위해 시스템에 로그인할 수 있으며 더 이상 필요하지 않을 때 로그아웃할 수 있다.

 


 

로그인의 기원

 

우리는 매일, 하루에도 수십 번씩 로그인을 합니다.

하지만 왜 로그인이라고 부르는지 모릅니다.

 

로그인의 뜻을 생각해보면 이상하게 들릴 수 있는 문구입니다.

온라인에서 많이 사용돼서 흔히 어떤 서비스에 자신의 계정으로 시작한다는 의미 정도로 알고 있지만,

로그인이라는 단어의 유래를 깊게 찾아본 사람은 많지 않습니다.

 

 

OED에서 "컴퓨터에 대한 온라인 엑세를 여는 것"이라는 현대적 의미의 "로그인"을 사용한 최초의 사례는 1963년 출판물인 MIT의 Compatible Time-Sharing System에서 가져온 것입니다.

이 시점이 "로그인"의 첫 번째 사용인지 확실하지 않지만 1961년에 시작된 CTSS가 틀림없이 최초의 시분할 운영 체제라면 말이 됩니다.

가장 먼저 로그인해야 하는 시스템이기도 합니다. (이전에는 일괄 처리 시스템만 있었습니다.)

 

CTSS든 비슷한 시스템이든, 1959년에서 1961년 사이에 MIT에 있는 엔지니어가 그들이 만들고 있는 시스템에 대한 새로운 사용자 명령을 설명할 필요가 있다고 생각합니다.

우리는 이러한 상황에서 많은 신조어를 얻으며, 역사상 로그인이 시작된 날은 지금 이 순간부터입니다.

 

 

시분할 시스템 이전에 '로그인'이 컴퓨터 이외의 의미로 사용되었을 가능성도 있지만, 다른 자료들에서 찾을 수 없었습니다.

그러나 물론 어떤 것 또는 누군가를 기록한다는 의미의 "로그" 부분은 컴퓨터보다 수백 년 앞서 있었습니다.

 

 

이 사용법은 OED에서 정의하는 "로그북" 또는 선박의 로그(또는 스타플릿에 있는 경우 선장 로그)에 무언가를 입력하는 것의 단축됩니다.

항해일지에서 선박의 항해 내용(일지에 표기된 선박의 진행률 포함)을 매일 입력하는 책입니다.

log-book 또는 logbook의 첫 번째 나열된 사용법은 대략 1689년부터입니다( J. Moore's  New Syst. Math ). 

250년 전으로 돌아가면서, 우리는 컴퓨터 시스템의 우리 자신을 확인하는 것에서 항해하는 배의 속도를 책에 입력하는 것으로 변했습니다.

그런데 왜 로그북이라고 불렸을까요? 칩 로그, 선박 로그, 로그 등으로 다양하게 불리는 이 장치 때문에 다음과 같습니다.

 

 

통나무 아니면 매듭(knot)이 있는 밧줄에 달린 무거운 나무 조각을 배 밖으로 던져서 정해진 시간 동안 얼마나 많은 매듭(knot)이 지나가는지, Wikipedia에서 설명하는 대로 시간을 측정합니다.

예전에는 배의 속도를 확인할 때 미리 밧줄에 매듭 표시를 한 부표를 던져놓고 일정 시간이 지난 후 그동안 몇 매듭(knot)이 지나갔다 확인합니다. 그 매듭(knot)의 수가 배의 속도였습니다.

 

 

이미지 참조 :&amp;nbsp;https://en.wikipedia.org/wiki/Chip_log

 

이것이 우리가 여전히 항해 속도를 매듭으로 측정하는 이유이기도 합니다.

그래서 컴퓨터로 어떤 서비스에 접속하여 기록을 남기기 시작할 때 "Login" 한다고 말하거나, 접속을 종료할 때 "Logout"을 사용하게 되었다고 추측합니다.

하지만 일반인들이 평소에 사용하던 단어가 아니고 단어만 들었을 때 이해되지 않았습니다.

그래서 "Login"이라는 단어가 낯설게 다가와서 "Signin"으로 사용하거나,

"Logout" 대신 "Signout" 등 같은 의미지만 다른 단어들이 사용되고 있습니다.

 

 

후기

 

"Login"이라는 단어를 외래어처럼 사용했는데, 유래를 알고 나니 되게 재밌습니다.

사실 이렇게 파고든 이유는 개발에 영향이 큽니다.

예전에는 Spring을 사용하면 그냥 이름이 Spring이고 사람들이 많이 사용하니 좋은 프레임워크구나 싶어서 사용을 했는데, Spring의 유래와 프레임워크의 유래까지 찾다 보니 새로운 사실들을 많이 알게 되었고 뿐만 아니라 다른 프레임워크를 사용하지 않고 왜 Spring을 사용하는지까지 찾다보니 이렇게 한 가지에 파고드는 습관이 생겼습니다.

단순히 사람들이 사용하니깐 사용하는 것이 아닌 무언가 발명이 되었으면 발명이 된 이유가 있을 것이고, 그게 지금 나에게 왜 필요한지까지 생각해보게 되었습니다.

감사합니다.

 

참조 자료: http://www.designcult.org/2011/08/why-do-we-call-in-logging-in.html

참조 자료: https://ko.wikipedia.org/wiki/%EB%A1%9C%EA%B7%B8%EC%9D%B8

'스프링 > LOG' 카테고리의 다른 글

[LOG] log4j 보안취약점 업데이트 권고 내용  (0) 2021.12.30

+ Recent posts