다음 코드를 보자

public class Main {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(10);
        list.add(20);
        list.add("30");

        Integer i = (Integer) list.get(2);
        System.out.println(list);
    }
}
  • list.add("30")을 넣었을때 그리고 i 형변환을 진행했을때 컴파일에러는 나지 않는다.
  • 하지만 RUN을 동작시켜보면 형변환에러 실행시 에러가 발생한다
  • 이것은 만약 애플리케이션에서 발생했다면 시스템이 돌아가지 않는 현상이 발생하는 것이다.

 

ArrayList<Integer> list = new ArrayList();
  • 그렇기 때문에 타입을 명시해주기 위해 ArrayList<Object타입>을 명시해준다.
  • list.add(10)은 따라서 (Integer)10으로 형변환이 되어 들어가고
  • 컴파일에러로 "30"은 list에 넣을 수 없다.
  • 컴파일에러로 발견 할 수 있다.

 

Integer i = //(Integer)// list.get(2);
  • 또한 형변환을 굳이 할 필요가 없다.
  • 이미 위에서 타입을 명시하고 있기 때문이다.

콜백

콜백 메소드란 다른 함수에 인수로 전달되는 함수이며, 이벤트 후에 실행되는 것을 말한다.

어떠한 행위를 하면 자동으로 실행되는 함수를 말하는 것

 

public class Main {
    public static void FirstMethod(){
        System.out.println("FirstMethod 호출");
        CallbackMethod();
    }
    public static void CallbackMethod(){ //callback 함수
        System.out.println("콜백함수 호출");
    }
    public static void main(String[] args) {
        FirstMethod();
    }
}
  • FirstMethod를 실행했을 때 Callback가 자동으로 실행되는 것을 볼 수 있다.
  • 즉 어떠한 행위를 했을떄(FirstMethod 호출했을때) 콜백함수(CallbackMethod)가 콜이 되는 것을 살펴볼 수 있다.

 

예시를 들어보자

public interface Strategy {
    public abstract void runStrategy();
}
public class StrategyGun implements Strategy{
    @Override
    public void runStrategy() {
        System.out.println("탕, 타당, 타다당");
    }
}

 

public class Soldier {
    void runContext(Strategy strategy){
        System.out.println("전투 시작");
        strategy.runStrategy();
        System.out.println("전투 종료");
    }
}
  • Soldier 클래스의 runContext에서 runStrategy를 호출하고 있다.

 

public class Client {
    public static void main(String[] args) {
        Strategy strategy = null;
        Soldier rambo = new Soldier();

        strategy = new StrategyGun();
        rambo.runContext(strategy);

    }
}
  • rambo.runContext을 호출 할때(어떠한 행위를 했을때) runStrategy(callback 자동으로 수행된다)도 호출이 된다.
  • 결국 runStrategy는 callback 함수이다.

Client을 함수를 조금 변형시켜보자.

public class Client {
    public static void main(String[] args) {
        Strategy strategy = null;
        Soldier rambo = new Soldier();
        Soldier rambo2 = new Soldier();

        strategy = new StrategyGun();
        rambo.runContext(new Strategy() {
            @Override
            public void runStrategy() {
                System.out.println("칼 칼칼");
            }
        });
        
        rambo2.runContext(new Strategy() {
            @Override
            public void runStrategy() {
                System.out.println("총총총");
            }
        });

    }
}
  • runContext을 호출할때 익명클래스로 구현했다.
  • 이 경우 중복 코드가 발생할 수 있다.
  • Soldier에서 리팩토링을 해볼수도 있다.

 

public static void main(String[] args) {
        Strategy strategy = null;
        Soldier rambo = new Soldier();

        strategy = new StrategyGun();
        rambo.runContext("총총총");

    }
}
public class Soldier {
    void runContext(String weaponSound){
        System.out.println("전투 시작");
        executeWeapon(weaponSound).runStrategy();
        System.out.println("전투 종료");
    }

    private Strategy executeWeapon(final String weaponSound){
        return new Strategy() {
            @Override
            public void runStrategy() {
                System.out.println(weaponSound);
            }
        };
    }
}
  • Strategy을 받아오는 것이 아닌 String을 받도록 한다.
  • Strategy의 runStrategy메소드는 executeWeapon호출되었을때 실행된다.
  • 다시 한 번 말하면 runStrategy 메소드는 callback 함수가 된다.

 

콜백 함수는 전략 패턴의 일종으로 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용된 설계 패턴이다.

 

전략패턴에 궁금하다면 https://jung-mmmmin.tistory.com/58

 

[Java] Template Method/Factory Method/Strategy/Template Callback 패턴

Template Method 패턴 " 하위 클래스에서 구체적으로 처리해라 " 상위클래스 : 템플릿에 해당하는 메소드가 정의, 정의 안에는 추상 메소드가 사용되고 있다. 추상 메소드의 정의만 알 수 있다. -> 정

jung-mmmmin.tistory.com

참고바란다.

Template Method 패턴

" 하위 클래스에서 구체적으로 처리해라 " 
  • 상위클래스 : 템플릿에 해당하는 메소드가 정의, 정의 안에는 추상 메소드가 사용되고 있다.
    • 추상 메소드의 정의만 알 수 있다. -> 정의부/ 처리의 뼈대 결정
  • 하위클래스 : 추상 메소드를 실제로 구현하는 것
    • 추상 메소드 구현으로 구체적은 처리가 결정된다. -> 구현부
  • 서로 다른 하위 클래스가 서로 다른 구현을 실행하면 서로 다른 처리가 실행 가능하다.

 

AbstractDisplay
open
print
close
display

< 상위 클래스 >

CharDisplay StringDisplay
open
print
close
open
print
close
printLine

< 하위 클래스 >

  • 이때, open, print, close(abstract method)로 하위클래스에서 구현을 해줄것
  • display는 상위에서 구현을 함
public abstract class AbstractDisplay {
    public abstract void open();
    public abstract void print();
    public abstract void close();
    public final void display(){
        open();
        for (int i = 0; i < 5; i++) {
            print();
        }
        close();
    }
}

 

 

public class CharDisplay extends AbstractDisplay{
    private char ch;
    public CharDisplay(char ch) {
        this.ch = ch;
    }

    @Override
    public void open() {
        System.out.print("<<");
    }

    @Override
    public void print() {
        System.out.print(ch);
    }

    @Override
    public void close() {
        System.out.println(">>");
    }
}
public class StringDisplay extends AbstractDisplay{
    private String string;
    private int width;
    
    public StringDisplay(String string) {
        this.string = string;
        this.width = string.getBytes().length;
    }

    @Override
    public void open() {
        printLine();
    }

    @Override
    public void print() {
        System.out.println("|" + string + "|");
    }

    @Override
    public void close() {
        printLine();
    }

    public void printLine(){
        System.out.print("+");
        for (int i = 0; i < width; i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}

 

public class Main {
    public static void main(String[] args) {
        AbstractDisplay d1 = new CharDisplay('H');
        AbstractDisplay d2 = new StringDisplay("Hello, world.");
        AbstractDisplay d3 = new StringDisplay("안녕하세요!");
        d1.display();
        d2.display();
        d3.display();
    }
}
  • 큰 틀은 상위클래스인 AbstractDisplay 
  • d1, d2는 각각 자신의 클래스에서 구현한 open, close, print 함수로 호출된다.
<<HHHHH>>
+-------------+
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+-------------+
+----------------+
|안녕하세요!|
|안녕하세요!|
|안녕하세요!|
|안녕하세요!|
|안녕하세요!|
+----------------+

 

  • runtime시 타입객체가 아닌 실제 들어있는(CharDisplay/StringDisplay) 객체 호출 -> 하위클래스는 상위클래스형이 될 수 있다.

 

정리

 

AbstractClass(추상 클래스의 역할) ConcreteClass(구현 클래스의 역할)
- 템플릿 메소드를 만든다.
- 추상 메소드가 있는 곳
- 추상 메소드는 하위클래스에서 구현
- 추상 메소드를 구체적으로 구현
- 상위 클래스의 견본 메소드에서 하위 클래스가 오버라이딩한 메소드를 호출 

 

 


Factory Method 패턴

" 오버라이드된 메소드가 객체를 반환하는 패턴" 

인스턴스를 만드는 방법은 상위 클래스에서 결정하지만 구체적인 내용은 하위 클래스 측에서 수행한다.

-> 인스턴스 생성을 위한 골격과 실제의 인스턴스 생성의 클래스를 분리해서 생각할 수 있다.

Factory Product
create - 템플릿 메소드
createProduct
registerProduct
use
  • Factory 클래스에서 Product 객체를 생성해주고 있다.
IDCardFactory IDCard
owners - 변수
createProduct
registerProduct
getOwners
owner - 변수
use
getOwner

 

  • IDCardFactory 클래스에서 IDCard 객체 생성하고 있다.

 

 

public abstract class Factory {
    public final Product create(String owner){
        Product p = createProduct(owner);
        registerProduct(p);
        return p;
    }

    protected abstract Product createProduct(String owner);
    protected abstract void registerProduct(Product product);
}
public abstract class Product {
    public abstract void use();
}

 

 

 

public class IDCardFactory extends Factory {
    private List owners = new ArrayList<>();
    @Override
    protected Product createProduct(String owner) {
        return new IDCard(owner);
    }
    @Override
    protected void registerProduct(Product product) {
        owners.add(((IDCard)product).getOwner());
    }
    public List getOwners(){
        return owners;
    }
}
public class IDCard extends Product {
    private String owner;
    IDCard(String owner){
        System.out.println(owner + "의 카드를 만듭니다.");
        this.owner = owner;
    }

    @Override
    public void use() {
        System.out.println(owner + "의 카드를 사용합니다.");
    }

    public String getOwner() {
        return owner;
    }
}

 

public class Main {
    public static void main(String[] args) {
        Factory factory = new IDCardFactory();
        Product card1 = factory.create("홍길동");
        card1.use();
    }
}
  • Factory create 실행
            Product p = createProduct(owner);
            registerProduct(p);
            return p;
  • createProduct 하위클래스에서 구현되어있으므로 IDCardFactory createProduct/registerProduct 실행.
  •      protected Product createProduct(String owner) {
            return new IDCard(owner);
        }
  • protected void registerProduct(Product product) {
            owners.add(((IDCard)product).getOwner());
        }
  • IDCard 객체 생성 -> 생성자 호출
    • IDCard(String owner){
              System.out.println(owner + "의 카드를 만듭니다.");
              this.owner = owner;
          }
          

각 클래스의 역할

Factory Product
Product 역할과 인스턴스 생성 인스턴스가 가져야 할 인터페이스(API)를 결정 -> 추상클래스 

 

 

 

IDCardFactory IDCard
구체적인 제품을 결정 IDCard 클래스 객체 호출 구체적인 제품을 만듬 IDCard 생성자 만들어지고 use 메소드호출

 

 

 

정리

팩토리 메소드 패턴은 의존 역전 원칙(DIP)를 활용하고 있음을 알 수 있다.
예를 들어 TV Product 클래스와 TV공장 TVFactory 만든다고 할 때 상위 클래스의 내용을 수정하지 않아도 된다.
상위클래스에는 TV Product, TVFactory 구체적인 클래스 이름이 들어있지 않음 -> 상위클래스가 포함된 패키지에 의존하고 있지 않다.
하위클래스만 필요에 따라 수정을 해주면 된다.

 

 


Strategy Pattern 패턴

"같은 문제를 다른 방법으로 해결 가능하다." 
  • 알고리즘을 구현한 부분을 모두 교환할 수 있다.
    • 전략 메소드를 가진 전략 객체
    • 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
    • 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자)

예시) 보급장교가 군인에게 무기를 보급, 군인은 무기 사용

  • 전략 객체 : 무기
  • 컨텍스트 : 군인
  • 클라이언트 : 보급장교
Strategy
runStrategy

 

 

StrategyGun StrategySword StrategyBow
runStrategy 실행 시 : 탕, 타당, 타다당 runStrategy 실행 시 : "챙.. 채쟁챙 챙챙" runStrategy 실행 시 : 슝.. 쐐액.. 쉑, 최종병기

 

 

Soldier
runContext

 

 

public static void main(String[] args) {
        Strategy strategy = null;
        Soldier rambo = new Soldier();

        strategy = new StrategyGun();
        rambo.runContext(strategy);

        System.out.println();

        strategy = new StrategySword();
        rambo.runContext(strategy);

        System.out.println();

        strategy = new StrategyBow();
        rambo.runContext(strategy);
    }


전투 시작
탕, 타당, 타다당
전투 종료

전투 시작
챙.. 채쟁챙 챙챙
전투 종료

전투 시작
슝.. 쐐액.. 쉑, 최종병기

 

 

일부러 Strategy 역할을 만들 필요가 있을까?

개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용된 패턴
  • 템플릿 메서드와 차이
    • 같은 문제의 해결책으로 상속을 사용하는 템플릿 메서드
    • 객체 주입을 통한 전략 패턴 중에서 선택/적용 가능
      • 단일 상속만 가능한 자바에서 상속이라는 제한이 있는 템플릿 메서드 패턴보다 전략 패턴을 더 많이 쓴다.
  • 수정과 교체가 용이하다.
    • 상위는 수정없이 하위만 수정을 하면 된다. 위임이라는 느슨한 연결을 사용하고 있어 알고리즘을 용이하게 바꿀 수 있다.
  • 실행 중 전략패턴의 하위 클래스를 바꿀 수 있다.
    • 메모리가 적은 환경에서는 SlowbutLessMemoryStrategy(속도는 느리지만 메모리를 절약하는 전략) 을 사용하고
    • 메모리가 많은 환경에서는 FastButMoreMemoryStrategy(속도는 빠르지만 메모리를 많이 사용하는 전략)
  • 검산에 사용 할 수 있다.
    • 한쪽의 알고리즘을 다른 쪽 알고리즘의 검산에 이용할 수 있다.
      • 표 계산 소프트웨어의 디버그 판에서 복잡한 계산을 실행할 때
        • 버그가 있을지도 모르는 고속의 알고리즘과 / 저속이지만 확실한 계산을 실행하는 알고리즘을 준비해 검산

 


Template Callback Pattern

  • DI에서 사용하는 특별한 형태의 전략 패턴의 변형
  • 전략 패턴과 모두 동일하지만 전략을 익명 내부 클래스로 정의해서 사용

rambo.runContext(strategy); 

 

rambo.runContext(new Strategy(){

     @Override

      public void runStrategy(){

              System.out.printlnl("총! 총총총총!");

      }

});

 

 

리팩터링된 코드

 

public class Soldier {
    void runContext(Strategy weaponSound){
        System.out.println("전투 시작");
        executeWeapon(weaponSound).runStrategy();
        System.out.println("전투 종료");
    }
    
    private Strategy executeWeapon(final String weaponSound){
        return new Strategy(){
            @Override
            public void runStrategy(){
                System.out.println(weaponSound);
            }
        };
    }
}

'문법 > Java' 카테고리의 다른 글

[Java] Generics(제너릭) & 타입변수  (0) 2023.12.08
[Java] 콜백(callback)함수  (1) 2023.12.08
[Java] 추상화(Abstraction)  (2) 2023.10.31

추상화 : 구체적인 것을 분해해서 관찰자가 관심 있는 특성만 가지고 재조합하는 것

 

클래스(class) : 같은 특성을 지닌 여러 객체를 총칭하는 집합의 개념

객체(instance) : 유일무이(unique)한 사물, 클래스의 인스턴스

 

클래스:객체 = 사람:김연아 = 사람:홍길동

 

ex) 사람 클래스를 만들기 위해 주변에 보이는 공통적인 특성

 시력, 몸무게, 혈액형, 키, 나이 등 명사로 표현되는 것 -> 속성(값)

 먹다, 자다, 일하다 , 침 뱉다. -> 기능 행위(함수)

class 사람 {
       float 시력;
       int 몸무게;
       int 키;
       void 먹다{...}
       void 일하다{...}

 

만약 애플리케이션을 만들었는데 사람에 대한 클래스를 이용해 객체를 만들때 필요없는 속성과 기능들이 있을 것이다.

병원 애플리케이션과 은행 애플리케이션으로 예시를 들어보면

병원 어플리케이션 은행 어플리케이션
class 사람 { class 사람 {
       float 시력;
       int 몸무게;
       int 키;
       int 나이;
       String 직업;
       String 연봉;
       float 시력;
       int 몸무게;
       int 키;
       int 나이;
       String 직업;
       String 연봉;
       void 먹다{...}
       void 일하다{...}
       void 입금하다{...}
       void 출금하다{...}
       void 먹다{...}
       void 일하다{...}
       void 입금하다{...}
       void 출금하다{...}

 

추상화는 모델링이다. 라고 하는데 모델은 추상화를 통해 실제 사물을 단순하게 묘사하는 것

 

추상화의 개념을 넓게 보자면

  • 상속을 통한 추상화, 구체화
  • 인터페이스를 통한 추상화
  • 다형성을 통한 추상화

 

클래스 객체_참조_변수 =  new 클래스();

Person 학생 = new Person();

 

 

미키마우스 제리
성명 : 미키마우스
국적 : 미국
나이 : 87
종교 : 무교
신장 : 70cm
체중 : 11.5 kg
성명 : 제리
국적 : 미국
나이: 75
종교 : 기독교
친구 : 톰
여자친구: null
달리다()
먹다()
휘파람 불다()
데이트하다()
울다()
달리다()
먹다()
장난치다()

추상화

성명
나이
달리다()
먹다
public class Mouse{
	public String name;
    public int age;
    
    public void sing(){
    	System.out.println(name + "찍찍"!!");
    }​

 

public class MouseDriver{
	public static void main(String[] args){
    	Mouse mickey = new Mouse();
        mickey.name = "미키";
        mickey.age = 85;
        mickey.countOfTail = 1;
        
        mickey.sing();
        
        Mouse jerry = new Mouse();
        
        jerry.name = "제리";
        jerry.age = 73;
        jerry.countOfTail = 1;
        
        jerry.sing()
   }
}

main 메서드 실행되기 직전에 메모리

스태틱 영역 

함수 정보, 속성 정보만 저장
name
age
sing()
 

이 때 name, age 속성은 Mouse 클래스에 속한 속성이 아닌 Mouse 객체에 속한 속성이기 때문에 객체가 생성되야만 값을 저장하기 위해 스태틱 영역이 아닌 힙 영역에 할당된다.

 


Static 

  • 스태틱 영역에 올라간 정보는 main() 메서드가 시작되기 전에 올라가서 main() 메서드가 종료된 후에 내려올 정도로 스태틱 영역에 고정되어 있다.
  • 다음과 같은 질문을 던져본다.
    • 미키마우스의 꼬리는 몇개인가?
    • 제리의 꼬리는 몇개인가?
    • 쥐의 꼬리는 몇개인가?

-> 답은 1개이다.

만약 이것을 static 이 아닌 객체의 속성으로 구현하게 된다면 각각 메모리에 하나씩 잡히게 된다.

Mouse
name
꼬리 
Mouse(micky)
name : micky
꼬리: 1 
Mouse(jerry)
name: jerry
꼬리: 1

스태틱을 구현하게 된다면 스태틱 영역에 단 하나의 저장 공간을 갖게 되는 것이다.

Mouse
name
꼬리 
Mouse(micky)
name : micky
Mouse(jerry)
name: jerry
public class Mouse {
	public String name;
    public int age;
    public static int countOfTail = 1;
    
    public void sing(){ 
    	System.out.println(name + "찍찍"
    }
}

 

클래스로 선언된 속성은 객체.속성이 아닌 Mouse.countOfTail 로 접근이 가능하다

 

클래스 멤버 = static 멤버 = 정적 멤버

객체 멤버 = 인스턴스 멤버

 

정적 멤버 속성은 클래스의 모든 객체들이 같은 값을 가질 때 사용하는 것이 정석.

또한 정적 멤버/메소드는 객체가 아닌 클래스에 속해 있으며, 클래스는 JVM 구동 시 메모리의 스태틱 영역에 바로 배치되기 때문에 객체의 존재 여부와 상관없이 쓸 수 있다.

 

정적 변수에 대한 접근 지정자 접근 메소드(getter)와 설정 메서드(setter)로 사용되어진다.

 

정적 속성은 스태틱 영역에 클래스가 배치될 때 클래스 내부에 메모리 공간을 확보하고 있지만

객체 속성(이름, 나이)는 속성명만 있고 실제 메모리 공간은 확보해 놓지 않는다. 힙 영역에 객체가 생성되면 그때 각 객체안에 멤버 속성을 위한 메모리 공간이 할당된다.

 

  non - static 멤버 = 객체용 멤버 static 멤버 = 클래스용 멤버
공간적 특성 멤버들은 객체마다 독립적으로 별도 존재 클래스 당 하나마 생성/ 객체 내부가 아닌 별도 공간에 생성
시간적 특성 속성과 메소드는 객체 생성 후 사용 가능 클래스가 로딩될 때 공간 할당
객체가 사라져도 멤버는 사라지지 않음
프로그램 종료 시 사라짐
공유 특성 멤버들은 다른 객체에 의해 공유되지 않음(비공유적) 동일한 클래스의 모든 객체에 의해 공유
class StaticSample{
	int n;
    void g() {...}
    static int m;
    static void f() {...}
  • 함수 g()는 non-static이기 때문에 어떤 객체가 호출했는지 암 -> this 사용가능
  • 속성 m과 함수 f()는 static 이기 때문에 누가 호출했는지 모름 -> this 사용 불가 / 클래스이름.m 으로 호출 해야함

 

static 메소드의 제약조건

  • static 메소드는 non-static 멤버(일반 객체 맴버)에 접근할 수 없음
    • 객체가 생성되지 않는 상황에서 해당 메소드는 실행 될 수 없기 때문
  • non-static 메소드는 static 멤버 사용 가능
    • 모든 객체가 공유하기 때문
  • this 사용 불가
    • 어떤 객체가 가르키는 지 this 래퍼런스를 사용 할 수 없음.

 

지역 변수 vs 멤버 변수

지역 변수 : 스택 영역에 저장 / 한 지역에서만 쓰는 변수로 초기화가 반드시 필요하다. 초기화가 없으면 쓰레기 값을 가짐 / 한 지역에서 사용되고 소멸되기 때문에 초기화 하는 것이 논리적으로 맞다.

멤버 변수 : 객체 메서드가 공유하는 변수 / 초기화 없이 자동으로 초기화가 된다.

 

+ Recent posts