Burninghering's Blog
article thumbnail
Published 2022. 3. 8. 22:39
CH02.디자인 패턴 Spring

디자인패턴이란?

자주 사용하는 설계 패턴을 정형화하여,

이를 유형별로 가장 최적의 방법으로 개발 가능하도록 정해둔 설계

 

GOF(Gang of Four) 디자인 패턴

소프트웨어 설계 시 기존 경험이 매우 중요하지만, 모든 사람들이 다양한 경험을 가질 수 없다.

이러한 지식을 공유하기 위해 나온 것이 GOF 디자인패턴!

 

객체지향 개념에 따른 설계 중, 재사용할 경우 유용한 설계를 디자인패턴으로 정리!

 

장점

-개발자간 원활한 소통

-소프트웨어 구조 파악 용이

-재사용을 통한 개발 시간 단축

-설계 변경 요청에 대한 유연한 대처

 

단점

-객체지향 설계/구현 : 그 전에 객체지향을 잘 이해하고 있어야 하기 때문

-초기 투자 비용(개발 시간) 부담 : 초기에 설계,코딩하는 부담...(인터페이스, 추상클래스..등등)


1. 생성 패턴

객체를 생성하는 것과 관련된 패턴

객체의 생성과 변경이 전체 시스템에 미치는 영향 최소화/코드 유연성 높임

  •  Factory Method
  •  Singleton
  •  Prototype
  •  Builder
  •  Abstract Factory
  •  Chaining

2. 구조 패턴

프로그램 내 자료구조나 인터페이스 구조 등 프로그램 구조를 설계하는데 활용될 수 있는 패턴

클래스, 객체들의 구성 통해 더 큰 구조를 만들 수 있게 해준다

큰 규모의 시스템에서는 많은 클래스들이 서로 의존성을 가지게 되는데, 이런 복잡한 구조를 개발하기 쉽게 만들어주고, 유지보수하기 쉽게 만들어줌

  •  Adapter
  •  Composite
  •  Bridge
  •  Decorator
  •  Facade
  •  Flyweight
  •  Proxy

3. 행위패턴

반복적으로 사용되는 객체들의 상호작용을 패턴화한 것

클래스나 객체들이 상호작용하는 방법과 책임을 분산하는 방법 제공

행위관련 패턴을 사용하여 독립적으로 일을 처리하고자 할 때 사용 

  •  Template Method
  •  Interpreter
  •  Iterator
  •  Observer
  •  Strategy
  •  Visitor
  •  Chain of responsibility
  •  Comman
  •  Mediator
  •  State
  •  Memento

Singleton pattern

어떠한 클래스(객체)가 유일하게 "1"개만 존재해야 할 때 사용

(여러 컴퓨터가 하나의 프린터를 공유하듯)

(실제 프로그래밍에서는 TCP Socket 통신에서, 서버와 연결된 connect 객체(socket)에 주로 사용한다네요)

 

패턴 만드는 방법 : 

1.기본 생성자를 private로 막아버린다.

2.public static (클래스 이름) getInstance 함수를 만들어, 이 함수를 통해서만 객체를 가져오거나 만들 수 있게 한다. 

더보기

SocketClient.java

package com.company.design.singleton;

public class SocketClient {

    private static SocketClient socketClient = null; //자기 자신을 객체로 가지고 있어야 함

    public SocketClient(){} //기본 생성자로 생성자를 만들 수 없도록 private로 막아줌

    public static SocketClient getInstance(){ //생성되어있는 객체를 가지고 오거나, 객체가 없는 경우 새로 객체를 만들어줌
        //static은 여러 인스턴스에서 공통으로 사용하는 것

        if (socketClient==null){
            socketClient=new SocketClient();
        }
        return socketClient;
    }

    public void connect(){
        System.out.println("connect");
    }
}

Aclazz.java

package com.company.design.singleton;

public class Aclazz {

    private SocketClient socketClient;

    public Aclazz(){
    //    this.socketClient=SocketClient.getInstance(); //기본 생성자를 막아놨으므로, getInstance()함수를 통해 만들어야 함
    this.socketClient=new SocketClient(); //Aclazz에서 자기가 직접 객체를 생성
    }

    public SocketClient getSocketClient(){
        return this.socketClient;
    }
}

Bclazz.java

package com.company.design.singleton;

public class Bclazz {
    private SocketClient socketClient;

    public Bclazz(){
        //this.socketClient=SocketClient.getInstance();
        this.socketClient=new SocketClient(); ////Bclazz에서 자기가 직접 객체를 생성
    }

    public SocketClient getSocketClient(){
        return this.socketClient;
    }
}

Main.java

package com.company.design;

import com.company.design.singleton.Aclazz;
import com.company.design.singleton.Bclazz;
import com.company.design.singleton.SocketClient;

public class Main {

    public static void main(String[] args) {

        Aclazz aclazz = new Aclazz();
        Bclazz bclazz = new Bclazz();

        SocketClient aClient = aclazz.getSocketClient();
        SocketClient bClient = bclazz.getSocketClient();

        System.out.println("두 개의 객체가 동일한가?");
        System.out.println(aClient.equals(bClient));
    }
}

결과는 False가 나온다. 싱글톤 패턴을 만든 후,객체가 다름을 실험해보기 위해 주석처리를 하고 코드를 추가했다. 


Adapter pattern

호환성이 없는 기존 클래스의 인터페이스를 변환하여 재사용할 수 있도록 한다.

SOLID 중에서 개방 폐쇄 원칙(OCP)를 따른다.

(실생활에서 어댑터 100V<->200V 변경, 또는 돼지코!)

서로 비슷하지만(110V와 220V), 인터페이스가 맞지 않을 때,

중간에 어댑터를 두는 것을 어댑터 인터페이스라고 한다.

더보기

AirConditioner.java

package com.company.design.adapter;

public class AirConditioner implements Electronic220V {
    @Override
    public void connect() {
        System.out.println("에어컨 220V on");
    }
}

Cleaner.java

package com.company.design.adapter;

public class Cleaner implements Electronic220V{
    @Override
    public void connect() {
        System.out.println("청소기 220v on");
    }
}

HairDryer.java

package com.company.design.adapter;

public class HairDryer implements Electronic110V{
    @Override //오버라이드 안하면 에러가 난다
    public void powerOn() {
        System.out.println("헤어드라이기 110v on");
    }
}

Electronic110V.java(I)

package com.company.design.adapter;

public interface Electronic110V {
    void powerOn();
}

Electronic220V.java(I)

package com.company.design.adapter;

public interface Electronic220V {
    void connect();
}

SocketAdapter.java (어댑터 인터페이스)

package com.company.design.adapter;

import com.company.design.singleton.SocketClient;

public class SocketAdapter implements Electronic110V{ //110V->220V

    private Electronic220V electronic220V; //자기 자신을 연결시켜줄 220V를 가지고 있어야 함

    public SocketAdapter(Electronic220V electronic220V){ //생성자로 220V를 받자
        this.electronic220V=electronic220V;
    }

   @Override
    public void powerOn() { //이 함수를 통해 110V가 220V로 바뀌게 됨!
        electronic220V.connect();
    }
}

Main.java

package com.company.design;

import com.company.design.adapter.*;
import com.company.design.singleton.Aclazz;
import com.company.design.singleton.Bclazz;
import com.company.design.singleton.SocketClient;

public class Main {

    public static void main(String[] args) {
        HairDryer hairDryer = new HairDryer();
        connect(hairDryer);

        Cleaner cleaner = new Cleaner();
        Electronic110V adapter = new SocketAdapter(cleaner); //Electronic110V 인터페이스 타입을 가진 SocketAdapter의 인스턴스
        connect(adapter);

        AirConditioner airConditioner = new AirConditioner();
        Electronic110V airAdater = new SocketAdapter(airConditioner);
        connect(airAdater);
    }
    
    public static void connect(Electronic110V electronic110V){
        electronic110V.powerOn();
    }
}

Proxy pattern

proxy 뜻 : 대리인 (뭔가를 대신해서 처리하는 것)

Proxy Class를 통해서 대신 전달하는 형태로 설계되며, 실제 Client는 Proxy로부터 결과를 받는다.

Cache의 기능으로도 활용 가능

SOLID 중에서 개방폐쇄원칙(OCP), 의존 역전 원칙(DIP)를 따른다

인스턴스 생성 방법(갑자기 헷갈려서)

더보기
이쯤에서 살펴보는 자바의 인스턴스 생성방법...(Browser의 show()함수 리턴 부분이 헷갈려서)

 

- new는 클래스 타입의 인스턴스(객체)를 생성해주는 역할을 담당한다. 

- new 연산자를 통해 메모리(Heap 영역)에 데이터를 저장할 공간을 할당받고 그 공간의 참조값(reference value /해시코드)을 객체에게 반환하여 주고 이어서 생성자를 호출하게 된다.

- 인스턴스를 핸들하기 위해서는 new 연산자를 통해 참조값을 저장한 객체로만 접근이 가능하다.

더보기

IBrowser.java(I)

package com.company.design.proxy;

public interface IBrowser {
    Html show();
}

Browser.java

package com.company.design.proxy;

public class Browser implements IBrowser{
    private String url;

    public Browser(String url){
        this.url=url;
    }

    @Override
    public Html show() {
        System.out.println("browser loading html from : "+url);
        return new Html(url); //새로운 Html 파일 넘기기
    }
}

BrowserProxy.java

package com.company.design.proxy;

public class BrowserProxy implements IBrowser{
    private String url;
    private Html html;

    public BrowserProxy(String url){
        this.url=url;
    }

    @Override
    public Html show() {
        if(html==null){
            this.html=new Html(url);
            System.out.println("BrowerProxy loading html from : "+url);
        }
        System.out.println("BrowerProxy use cache html : "+url);
        return html;
    }
}

Html.java

package com.company.design.proxy;

public class Html {
    private String url;

    public Html(String url){
        this.url=url;
    }
}

Main.java

package com.company.design;

import com.company.design.proxy.Browser;
import com.company.design.proxy.BrowserProxy;
import com.company.design.proxy.IBrowser;


public class Main {

    public static void main(String[] args) {
        Browser browser = new Browser("www.naver.com");
        browser.show();
        browser.show();
        browser.show();
        browser.show(); // 매번 네이버로부터 url을 받아오고 있음

        IBrowser browser = new BrowserProxy("www.naver.com");
        browser.show(); //처음에는 url을 네이버에서 받아오지만
        browser.show(); //캐시 기능 사용해 불러오고 있다.
        browser.show();
        browser.show();
    }

}

Decorator pattern

기존 뼈대(클래스)는 유지하되, 이후 필요한 형태로 꾸밀 때 사용한다.

확장이 필요한 경우 상속의 대안으로도 활용한다. 

SOLID 중에서 개방폐쇄원칙(OCP)와 의존 역전 원칙(DIP)를 따른다.

케이크위에 초코/딸기 등을 얹는 것처럼

원본 위에 어떤 것을 첨가하느냐에 따라서 다른 것으로 확장이 되는 것을 말함

 

 

자바에서 상속을 할 때 몇 가지 룰이 존재한다. 그중 하나는 바로 슈퍼 클래스의 생성자를 서브 클래스에서 반드시 불러줘야 된다는 것이다.

 

11. 자바 상속(Inheritance) - this와 super

지난 포스트에서 했던 '상속'을 이용해 코드를 재사용할 수 있었다. 그런데 만약 수퍼 클래스와 서브 클래스에 같은 이름의 변수나 메서드가 있다면 어떡할까? 두 클래스에 있는 멤버 변수와 메

imasoftwareengineer.tistory.com

 

기본 베이스 Audi에 대해 등급이 올라갈 때마다 가격이 올라가는 Decorator를 통해 A3,A4,A5를 첨가하기

더보기

ICar.java(I)

package com.company.design.decorator;

public interface ICar {
    int getPrice();
    void showPrice();
}

Audi.java

package com.company.design.decorator;

public class Audi implements ICar{

    private int price;
    public Audi(int price){
        this.price=price;
    }

    @Override
    public int getPrice() {
        return 0;
    }

    @Override
    public void showPrice() {
        System.out.println("Audi의 가격은 "+price+"원 입니다.");
    }
}

AudiDecorator.java

package com.company.design.decorator;

public class AudiDeorator implements ICar{

    protected ICar audi;
    protected String modelName;
    protected int modelPrice;

    public AudiDeorator(ICar audi, String modelName,int modelPrice){
        this.audi=audi;
        this.modelName=modelName;
        this.modelPrice=modelPrice;
    }

    @Override
    public int getPrice() {
        return audi.getPrice()+modelPrice;
    }

    @Override
    public void showPrice() {
        System.out.println(modelName+"의 가격은 "+getPrice()+" 원 입니다.");
    }
}

A3.java

package com.company.design.decorator;

public class A3 extends AudiDeorator{

    public A3(ICar audi, String modelName) {
        super(audi, modelName, 2000);
    }
}

A4.java

package com.company.design.decorator;

public class A4 extends AudiDeorator{

    public A4(ICar audi, String modelName) {
        super(audi, modelName, 3000);
    }
}

A5.java

package com.company.design.decorator;

public class A5 extends AudiDeorator{

    public A5(ICar audi, String modelName) {
        super(audi, modelName, 4000);
    }
}

Main.java

public class Main {

    public static void main(String[] args) {
        ICar audi=new Audi(1000);
        audi.showPrice();

        //a3
        ICar audi3 = new A3(audi,"A3");
        audi3.showPrice();
        //a4
        ICar audi4 = new A4(audi,"A4");
        audi4.showPrice();
        //a5
        ICar audi5 = new A5(audi,"A5");
        audi5.showPrice();
        
    }

Observer pattern

변화가 일어났을 때, 미리 등록된 다른 클래스에 통보해주는 패턴을 구현한 것

많이 보이는 곳은 event listener에서 해당 패턴 사용

 

특정 이벤트(버튼 객체가 click하면 메세지 전달하기)를 Listener를 통해 전달해준다.

더보기

IButtonListener.java(I)

package com.company.design.observer;

public interface IButtonListener {
    void clickEvent(String event);
}

Button.java

package com.company.design.observer;

public class Button {

    private String name;
    private IButtonListener buttonListener;

    public Button(String name){
        this.name=name;
    }

    public void click(String message){
        buttonListener.clickEvent(message);
    }

    public void addListener(IButtonListener buttonListener){
        this.buttonListener=buttonListener;
    }

}

Main.java

public class Main {

    public static void main(String[] args) {

        Button button = new Button("버튼");

        button.addListener(new IButtonListener() {
            @Override
            public void clickEvent(String event) {
                System.out.println(event);
            }
        });

        button.click("메시지 전달 : click 1");
        button.click("메시지 전달 : click 2");
        button.click("메시지 전달 : click 3");
        button.click("메시지 전달 : click 4");

    }
}

Facade pattern

Facade = 건물 앞 쪽

건물 뒷 쪽은 모르는 형태를 말한다.

여러 개의 객체와 실제 사용하는 서브 객체의 사이에 복잡한 의존 관계가 있을 때,

중간에 facade 객체를 두고,

여기서 제공하는 interface만을 활용하여 기능을 사용하는 방식이다.

Facade는 자신이 가지고 있는 각 클래스 기능을 명확히 알아야 한다.

여러 가지의 객체 의존성들을 안 쪽으로 숨겨주는 형태의 패턴이다.

더보기

Ftp.java

package com.company.design.facade;

public class Ftp {
    private String host;
    private int port;
    private String path;

    public Ftp(String host,int port,String path){
        this.host=host;
        this.port=port;
        this.path=path;
    }

    public void connect(){
        System.out.println("FTP host : "+host+" Port : "+port+" 로 연결합니다.");
    }
    public void moveDirectory(){
        System.out.println("FTP path : "+path+" 로 이동합니다.");
    }
    public void disConnect(){
        System.out.println("FTP 연결을 종료 합니다.");
    }
}

Reader.java

package com.company.design.facade;

public class Reader {

    private String fileName;

    public Reader(String fileName){
        this.fileName=fileName;
    }

    public void fileConnect(){
        String msg=String.format("Reader %s 로 연결합니다.",fileName);
        System.out.println(msg);
    }

    public void fileRead(){
        String msg = String.format("Reader %s 의 내용을 읽어옵니다.",fileName);
    }

    public void fileDisconnect(){
        String msg=String.format("Reader %s로 연결 종료합니다.",fileName);
        System.out.println(msg);
    }

}

Writer.java

package com.company.design.facade;

public class Writer {

    private String fileName;

    public Writer(String fileName){
        this.fileName=fileName;
    }
    public void fileConnect(){
        String msg=String.format("Writer %s 로 연결합니다.",fileName);
        System.out.println(msg);
    }

    public void fileDisconnect(){
        String msg=String.format("Writer %s 로 연결 종료합니다.",fileName);
        System.out.println(msg);
    }

    public void write(){
        String msg=String.format("Writer %s 로 파일 쓰기를 합니다.",fileName);
        System.out.println(msg);
    }
}

 

파사드 객체

SftpClient.java

package com.company.design.facade;

public class SftpClient {

    private Ftp ftp;
    private Reader reader;
    private Writer writer;

    public SftpClient(Ftp ftp, Reader reader, Writer writer){//객체를 한번 감싸서, 의존성을 SftpClient가 다 가져감
        this.ftp=ftp;
        this.reader=reader;
        this.writer=writer;
    }

    public SftpClient(String host,int port, String path,String fileName){
        this.ftp=new Ftp(host,port,path);
        this.reader=new Reader(fileName);
        this.writer=new Writer(fileName);
    }
    //String host,int port, String path,String fileName 4가지 값만 받아도
    //SftpClient 객체를 사용할 수 있도록 오버로딩

    public void connect(){ //새로운 인터페이스 제공
        ftp.connect();
        ftp.moveDirectory();
        writer.fileConnect();
        reader.fileRead();
    }

    public void disConnect(){//새로운 인터페이스 제공
        writer.fileDisconnect();
        reader.fileDisconnect();
        ftp.disConnect();
    }

    public void read(){//새로운 인터페이스 제공
        reader.fileRead();
    }

    public void write(){//새로운 인터페이스 제공
        writer.write();
    }

}

 

Main.java

public class Main {

    public static void main(String[] args) {

        Ftp ftpClient = new Ftp("www.foo.co.kr",22,"/home/etc");
        ftpClient.connect();
        ftpClient.moveDirectory();

        Writer writer = new Writer("text.tmp");
        writer.fileConnect();
        writer.write();

        Reader reader = new Reader("text.tmp");
        reader.fileConnect();
        reader.fileRead();

        reader.fileDisconnect();
        writer.fileDisconnect();
        ftpClient.disConnect();
        //위 예시는 굉장히 추상화된 형태이지만, 각각의 객체에 너무 의존하고 있다.

        //그러므로 아래와 같이 파사드 패턴을 적용시키면,
        //앞 쪽 정면만 바라볼 수 있도록 객체 하나 만들 수 있고,
        //여러가지 의존성을 가진 것을 새로운 인터페이스 형태로 제공한다.
        SftpClient sftpClient = new SftpClient("www.foo.co.kr",22,"/home/etc","text.tmp");
        sftpClient.connect();
        sftpClient.write();
        sftpClient.read();
        sftpClient.disConnect();
    }
}

Strategy pattern

전략 패턴, 객체지향의 꽃이다.

유사한 행위들을 캡슐화하여, 객체의 행위를 바꾸고 싶은 경우

직접 변경하는 것이 아닌 전략만 변경하여 유연하게 확장하는 패턴

SOLID중에서 개방폐쇄 원칙(OCP)와 의존 역전 원칙(DIP)를 따른다.

아래 세 개의 요소가 필수!

  • 전략 메서드를 가진 전략 객체(Normal Strategy, Base64 Strategy -> 전략 2개)
  • 전략 객체를 사용하는 컨텍스트(Encoder)
  • 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(Main)
더보기

EncodingStrategy.java(I)

package com.company.design.strategy;

public interface EncodingStrategy {
    String encode(String text);
}

Encoder.java

package com.company.design.strategy;

public class Encoder {

    private EncodingStrategy encodingStrategy;

    public String getMessage(String message){
        return this.encodingStrategy.encode(message);
    }

    public void setEncodingStrategy(EncodingStrategy encodingStrategy){
        this.encodingStrategy=encodingStrategy;
    }
}

Base64Strategy.java

package com.company.design.strategy;

import java.util.Base64;

public class Base64Strategy implements EncodingStrategy{

    @Override
    public String encode(String text) {
        return Base64.getEncoder().encodeToString(text.getBytes());
    }
}

NomalStrategy.java

package com.company.design.strategy;

public class NomalStrategy implements EncodingStrategy{
    @Override
    public String encode(String text) {
        return text;
    }
}

AppendStrategy.java

package com.company.design.strategy;

public class AppendStrategy implements EncodingStrategy{

    @Override
    public String encode(String text) {
        return "ABCD"+text;
    }
}

Main.java

public class Main {

    public static void main(String[] args) {

        Encoder encoder = new Encoder(); //전략을 사용하기 위한 원본 객체(변하지 않음)

        //base64
        EncodingStrategy base64 = new Base64Strategy(); //전략 생성

        //normal
        EncodingStrategy normal = new NomalStrategy(); //전략 생성


        String message="Hello java";

        encoder.setEncodingStrategy(base64); //전략 사용할 때마다 세팅
        String base64Result = encoder.getMessage(message);
        System.out.println(base64Result);

        encoder.setEncodingStrategy(normal);
        String normalResult = encoder.getMessage(message);
        System.out.println(normalResult);

        encoder.setEncodingStrategy(new AppendStrategy());
        String appendResult = encoder.getMessage(message);
        System.out.println(appendResult);
        //전략을 주입함으로써 결과가 달라지도록 함함
    }
}

'Spring' 카테고리의 다른 글

Spring Boot REST API_PUT Method  (0) 2022.04.18
Spring Boot REST API_POST Method  (0) 2022.04.12
Spring Boot REST API_Get Method  (0) 2022.03.22
WEB 개발 개론  (0) 2022.03.09
CH01.객체지향  (0) 2022.02.18
profile

Burninghering's Blog

@개발자 김혜린

안녕하세요! 반갑습니다.