[JAVA] OOP 디자인 패턴(1)

INHEES·2023년 8월 19일
0

Design Pattern

목록 보기
1/3

JAVA 객체지향 디자인 패턴 : Singleton, Strategy, State, Command, Adapter

디자인 패턴의 중요성

  • 디자인 패턴을 중요하게 생각하는 이유는 디자인 패턴을 통해 프로그램 설계에 있어 추상화를 할 수 있다.
  • 디자인 패턴은 프로그램을 객체 관점보다 높은 레벨에서 생각할 수 있도록 도와준며, 장황한 코드를 이해하는데 시간을 획기적으로 줄일 수 있다.
  • 디자인 패턴을 알고 있다면 프로그래밍 구조 관점에서 프로그램에 대한 복잡한 의사소통을 간단한 단어로 해결 가능하다.

Singleton 패턴

Singleton 패턴은 객체의 인스턴스를 한개만 생성하게 하는 패턴이며, 최최 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다.

주로 공통된 객체를 여려개 생성해서 사용하는 상황에서 많이 사용된다.

  • 프로그램 내에서 하나의 객체만 존재한다.
  • 프로그램 내에서 여러 부분에서 하나의 객체를 공유해서 사용한다.

Singleton 패턴의 이점

  1. 하나의 인스턴스 만을 정적 메모리 영역에 생성 하기에 객체 접근에 있어 메모리 낭비를 방지
  2. 이미 생성된 인스턴스를 활용하기에 속도 측면에서 이점이 있음
  3. 전역으로 사용되는 인스턴스이기에 여러 클래스 에서 데이터를 공유하며 사용 가능하다.

Singleton 패턴 은 multi-thread 환경에서는 동시성 이슈가 발생할 수 있다. 물론 해결방법도 있으니 쓸 수 없는것은 아니다.

Singleton 패턴의 구현 예시

public class Settings{

	private Settings(){};
    private static Settings settings = null;
    
    public static Settings getSettings(){
    	if(settings == null){
        	settings = new Settings();
        }
        return settings;
    }
}
#################################
public class FirstPage{
	private Settings settings = Settings.getSettings(); //method 호출
}

코드를 살펴보면 private static 으로 Singleton 객체의 인스턴스를 선언후 다른 클래스에서 getSettings() 메서드가 처음 실행될때 settings 가 생성되고 아니라면 이미 생성 되었던 settings 인스턴스가 리턴된다.

기본 생성자 앞에 private 접근 제어자를 써주어 외부에서 새로운 객체의 생성을 막는 역할도 한다. 마찬가지로 static 변수로 선언된 settings 도 정적공간에 담기 위한 역할을 한다.

Strategy 패턴

같은 기능이지만 서로 다른 전략(method) 을 가진 클래스들을 캡슐화하여 상호 교환할 수 있도록 하는 패턴이다.
즉, 옵셜들마다의 행동들을 모듈화해서 독립적으로 상호 교체 가능하게 만드는 것이다.

Strategy 패턴의 구현 예시

public class MyProgram{
	private SearchButton searchButton = new SearchButton(this);
    
	public void setModeAll(){
    	searchButton.setSearchStrategy(new SearchStrategyAll());
    }
    ...
}

#######################################
interface SearchStrategy{
	public void serach();
}
class searchStrategyAll implements SearchStrategy{
	public void search(){...}
}
class searchStrategyImage implements SearchStrategy{
	public void search(){...}
}
#######################################
public class SerachButton{
    ....
    
    private SearchStrategy searchStrategy = new SearchStrategyAll();
    
	public void setSearchStrategy(SearchStrategy _searchStrategy){
    	searchStrategy = _searchStrategy;
    }
    public void onClick(){
    	searchStrategy.serach();
    }
}

코드를 살펴보면 SearchStrategy 인터페이스를 만들고 onClick 메서드에서 searchStrategy 의 serach() 함수만 상속받아서 사용한다.
setter 를 이용하여 searchStrategy를 SearchStrategy 인터페이스를 상속받은 다른 검색 전략으로 바꿀 수 있는 것이다.

State 패턴

객체가 상태에 따라 행위를 달리하도록 하는 패턴, 상태가 행위를 하게 한다는 의미로 동일한 동작을 객체의 상태에 따라 다르게 처리해할 때 사용된다.
State 패턴은 Strategy 패턴과 유사하며, 특정 상태마다 다르게 할일, 상태마다 할 일을 하나씩 모듈화해서 지정할 때 쓰인다.

즉, Strategy 패턴이, 지정된 특정 메소드가 모듈화된 모드에 따라 다르게 실행되도록 하는 것이라면, State 패턴은, 그 메서드가 실행될 때 모드가 전환되도록 하는 것이다.

State 패턴의 구현 예시

public class ModeSwitch{
	...
    
    public void on Switch()	{
    	modeState.toggle(this);
    }
}
###################################
public interface ModeState{
	public void toggle (ModeSwitch modeSwitch);
}
class ModeStateLight implements ModeState{
	public void toggle (ModeSwitch modeSwitch){
    	...
        modeSwitch.setState(new ModeStateDart());
    }
}
class ModeStateDark implements ModeState{
	public void toggle (ModeSwitch modeSwitch){
    	...
        modeSwitch.setState(new ModeStateLight());
    }
}

코드를 살펴보면 onSwitch() 함수가 실행되면 ModeStateLight 클래스의 toggle 함수가 실행되고 setState 함수에서 ModeStateDark 모드로 전환되는 것을 확인할 수 있다.

Command 패턴

실행될 기능을 캡슐화 함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴이다.

이벤트가 발생했을 때 실행될 기능이 다양하면서도 변경이 필요한 경우 이벤트를 발생 시키는 클래스 변경없이 재사용에 유용하다.
실행을 요구하는 호출자 클래스와 실제 기능을 실행하는 수신자 클래스 사이의 의존성을 제거한다.
즉 실행될 기능의 변경에서 호출자 클래스 수정없이 사용 가능하다.

Command 패턴 구현 예시

public class RobotKit {
  private Robot robot = new Robot();
  private ArrayList<Command> commands = new ArrayList<Command>();

  public void addCommand (Command command) {
    commands.add(command);
  }
  public void start () {
    for (Command command : commands) {
      command.setRobot(robot);
      command.execute();
    }
  }
}
#################################################

abstract class Command {
  protected Robot robot;

  public void setRobot (Robot _robot) {
    this.robot = _robot;
  }
  public abstract void execute ();
}

class MoveForwardCommand extends Command {
  int space;
  public MoveForwardCommand (int _space) { 
    space = _space; 
  }
  public void execute () {
    robot.moveForward(space);
  }
}

class TurnCommand extends Command {
  Robot.Direction direction;
  public TurnCommand (Robot.Direction _direction) {
    direction = _direction;
  }
  public void execute () {
    robot.turn(direction);
  }
}

class PickupCommand extends Command {
  public void execute () {
    robot.pickup();
  }
}
##############################################

public class Robot {
  public enum Direction { LEFT, RIGHT }

  public void moveForward (int space) {
    System.out.println(space + " 칸 전진");
  }

  public void turn (Direction _direction) {
    System.out.println(
      (_direction == Direction.LEFT ? "왼쪽" : "오른쪽") + "으로 방향전환"
    );
  }

  public void pickup () {
    System.out.println("앞의 물건 집어들기");
  }
}

코드를 살펴보면 Robot, RobotKit 클래스와 Command 추상클래스가 선언되어 있다.
Command 추상 클래스에서 execute 추상 메서드로 선언되어 있어 자식 클래스인 TurnCommand, PickupCommand 에서 기능이 구현된다.

여기서 추상클래스는 객체 생성이 불가하다. 때문에 Command 추상 클래스에서 Robot 객체를 setRobot() 메서드를 통해 값을 지정한다.

TurnCommand, PickupCommand 클래스에서 기능을 구현하여 RobotKit 클래스에서 기본 변수값으로 Robot의 Command를 상속한 클래스의 객체를 ArrayList 로 담는다.

Adapter 패턴

한 클래스의 인터페이스를 클라이언트에서 사용하고자하는 다른 인터페이스로 변환하며, 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스를 연결 가능하다.

인터페이스가 바뀌더라도 클라이언트와 구현된 인터페이스가 분리되어 있으므로 변경 내역은 어댑터에 캡슐화 되기에 클라이언트는 바뀔 필요가 없다.

  • 어댑터에서는 어댑터 인터페이스를 사용하여 그 요청을 어댑터 에 대한 하나 이상의 메서드를 호출하는 것으로 변화를 준다.

Adapter 패턴 구현 예시

package adapter.robot;

interface Order {
  public void run (Robot robot);
}

class MoveBackOrder implements Order {
  private int block;

  public MoveBackOrder(int _block) {
    block = _block;
  }

  public void run (Robot robot) {
    robot.turn(Robot.Direction.LEFT);
    robot.turn(Robot.Direction.LEFT);
    robot.moveForward(block);
  }
}

#########################################
class CommandOrderAdapter extends Command {
  private Order order;

  public CommandOrderAdapter (Order _order) {
    order = _order;
  }

  public void execute () {
    order.run(robot);
  }
}

#########################################
public class MyProgram{

	RobotKit robotkit = new Robotkit();
    ....
   	robotKit.addCommand(new CommandOrderAdapter(new MoveBackOrder(1));
}

코드를 살펴보면 위의 설명된 Command 패턴에 Adapter 를 적용하였다.

CommandOrderAdapter 클래스에서 멤버변수 Order 객체를 생성자로 받아온다. execute() 메서드가 실행되면 Order 객체의 run() 에 상속받은 내부 변수인 robot을 넣어서 메서드를 실행한다. Order 인터페이스를 상속받은 MoveBackOrder 클래스의 run() 메서드가 실행 되는 것이다.

마지막 어답터에 끼워 ArrayList 명령 리스트에 넣어주면 작동하게 된다.


참고 자료

https://www.youtube.com/watch?v=lJES5TQTTWE
https://www.youtube.com/watch?v=q3_WXP9pPUQ

profile
이유를 찾아보자

0개의 댓글