Information Security Study

240116 추상클래스(abstract 실습), 템플릿메서드, 인터페이스, has - a 관계 본문

네트워크 캠퍼스/JAVA

240116 추상클래스(abstract 실습), 템플릿메서드, 인터페이스, has - a 관계

gayeon_ 2024. 1. 16. 16:05

추상 클래스

 

abstract 예제

package abstract_.abs;

public abstract class PopupStore {

	/*
	 * 1. 메서드에 abstract를 붙이면 해당 메서드는 추상메서드가 되고
	 * 이 메서드에는 반드시 오버라이딩 해야 한다.
	 * 2. 추상 메서드는 상속을 목적으로 선언한 메서드이지 실행을 목적으로
	 * 선언된 메서드가 아니므로 메서드의 몸체({})부분이 없고
	 * 선언 마무리도 세미콜론으로 해야 한다.
	 * 3. 일반 클래스에는 추상 메서드를 선언할 수 없다.
	 * 추상메서드가 하나 이상 존재하면 무조건 추상클래스로 선언해야 한다.
	 * 4. 추상클래스 내부에서는 추상메서드가 하나 이상으로 존재한다면 일반 메서드 선언도 가능하다.
	 */
	
	public abstract void orderApple();
	public abstract void orderOrange();
	public abstract void orderGrape();
	
    // 오버라이딩이 필요 없는 메서드
	public void fefund() {
		System.out.println("제품에 문제가 있어서 환불합니다.");
	}
}
package abstract_.abs;

public class Store extends PopupStore{

	@Override
	public void orderApple() {
		System.out.println("착즙사과주스를 20000원에 팝니다");
		
	}

	@Override
	public void orderOrange() {
		System.out.println("착즙오렌지주스를 20000원에 팝니다");
		
	}

	@Override
	public void orderGrape() {
		System.out.println("착즙포도주스를 20000원에 팝니다");
		
	}

}
package abstract_.abs;

public class ConvenientStore extends PopupStore {

	@Override
	public void orderApple() {
		System.out.println("가당 사과주스를 4000원에 팝니다.");
		
	}

	@Override
	public void orderOrange() {
		System.out.println("가당 오렌지주스를 4000원에 팝니다.");
		
	}

	@Override
	public void orderGrape() {
		System.out.println("가당 포도주스를 4000원에 팝니다.");
		
	}

}
package abstract_.abs;

public class MainClass {

	public static void main(String[] args) {
		// PopupStore 클래스는 직접 객체 생성 불가능
		// PopupStore ps = new PopupStore();
		
		PopupStore s = new Store();
		// 객체 종류에 따라 실행 구문이 다르게 정의되었지만
		// 명세는 같은 메서드
		s.orderApple();
		s.orderGrape();
		s.orderOrange();
		
		// 어떤 객체가 와도 공통적으로 실행되는 메서드
		s.refund();

	}

}

 

메서드에 abstract를 붙이면 해당 메서드는 추상메서드가 되고 반드시 오버라이딩 해야 한다.

 

추상 메서드는 상속을 목적으로 선언한 메서드이지 실행을 목적으로 선언된 메서드가 아니므로 메서드의 몸체({})부분이 없고 선언 마무리도 세미콜론으로 해야 한다.
 

일반 클래스에는 추상 메서드를 선언할 수 없다.
 

추상메서드가 하나 이상 존재하면 무조건 추상클래스로 선언해야 한다.
 

추상클래스 내부에서는 추상메서드가 하나 이상으로 존재한다면 일반 메서드 선언도 가능하다.

 

 

* 객체지향적으로 코드를 작성해야 하는 이유

객체 생성 시 Store와 ConvenienvtStore로 타입만 변경만 해주면 아래와 같이 작성된 클라이언트 코드는 변경하지 않아도 된다.

객체 지향적으로 작성하지 않으면 객체가 변경될 때마다 클라이언트 코드도 수정해야 한다.

오버라이딩 된 메서드와 추상메서드는 메서드명이 모두 같지만, abstract 키워드가 없다면 메서드명을 달리 할 수 있기 때문에 객체가 변경될 때마다 클라이언트 코드를 수정하게 된다.

// 클라이언트 코드

		// 객체 종류에 따라 실행 구문이 다르게 정의되었지만
		// 명세는 같은 메서드
		s.orderApple();
		s.orderGrape();
		s.orderOrange();
		
		// 어떤 객체가 와도 공통적으로 실행되는 메서드
		s.refund();

 

템플릿 메서드

 

템플릿 메서드 예제

package abstract_.templatemethod;

public abstract class Lottery {

	// 템플릿 메서드 패턴은 큰 틀에서 호출구문은 구현메서드(실행문이 있는 메서드)
	// 로 정의하고 구현메서드가 호출하는 추상메서드들은 상속 후에 특징을 정하도록
	// 만들어서 호출 순서는 그대로 가져갈 수 있도록 하되
	// 사용자가 특징만 정의하도록 하는 디자인 패턴이다.
	
	// 구현메서드에는 큰 틀은 같지만 세부사항이 달라질 수 있는 내용을 먼저 작성한다.
	public final void lotteryCycle() { // final 이므로 오버라이딩 불가. 순서 고정
		// 1. 복권 구매 
		buyLottery();
		// 2. 당첨 여부 확인
		checkWinLottery();
		// 3. 당첨금 수령
		getLotteryMoney();
	}
	
	// 세부사항은 상속받은 주체가 무엇인지에 따라 다르게 정의할 수 있도록
	// 추상메서드만 정의해놓고 추가적인 작업은 하지 않는다.
	abstract void buyLottery();
	abstract void checkWinLottery();
	abstract void getLotteryMoney();
}
package abstract_.templatemethod;

public class KoreanLotto extends Lottery{

	@Override
	void buyLottery() {
		System.out.println("한 게임에 천원짜리 로또를 삽니다.");
	}

	@Override
	void checkWinLottery() {
    	System.out.println("45C6의 확률을 뚫고 1등에 당첨되었습니다.");
		
	}

	@Override
	void getLotteryMoney() {
		System.out.println("1등 상금으로 대략 수십억을 받았습니다.");
		
	}

}
package abstract_.templatemethod;

public class StatesSuperball extends Lottery{

	@Override
	void buyLottery() {
		System.out.println("미국가서 슈퍼볼 복권을 삽니다.");
		
	}

	@Override
	void checkWinLottery() {
		System.out.println("69C5 * 26C1 분의 1의 확률로 당첨되었습니다.");
		
	}

	@Override
	void getLotteryMoney() {
		System.out.println("당첨금액은 최소 수천억원입니다.");
		
	}

}
package abstract_.templatemethod;

public class MainClass {
	
	public static void main(String[] args) {
		// Lottery lottery = new KoreanLotto();
		Lottery lottery = new StatesSuperball();
		
		lottery.lotteryCycle();
	}

}

 

템플릿 메서드인 이유

템플릿처럼 메서드가 정해져있어 정의만하면 되기 때문에 템플릿 메서드라 부른다.

 

템플릿 메서드의 장점

유지, 보수성이 좋다.

설계한 사람의 의도대로 구현할 수 있다.

 

 

인터페이스(Interface)

  • 자바의 인터페이스는 객체의 사용 방법을 정의한 타입(메서드 명세서)으로 객체의 교환성을 높여주기 때문에 다형성을 구현하는 매우 중요한 역할을 한다.
  • 인터페이스를 선언할 때는 interface 키워드를 사용하며, 클래스에서 인터페이스를 구현할 때는 클래스 이름 뒤에 implements 키워드를 사용하여 구현한다.
  • 클래스는 멤버변수, 생성자, 메서드를 구성 멤버로 가지지만 인터페이스는 상수와 메서드만을 구성멤버로 가진다.
  • 인터페이스는 데이터를 저장할 수 없기 때문에 데이터를 저장할 객체 또는 정적 변수를 선언할 수 없다.
  • 따라서 인터페이스 선언된 변수는 public static final을 생략하더라도 컴파일 과정에서 자동으로 붙게 된다. (상수)
  • 인터페이스의 메서드를 추상메서드 형식으로 선언하면 abstract를 붙이지 않더라도 자동으로 컴파일 과정에서 붙게 된다. (추상메서드)
  • 클래스가 상속 가능한 것처럼 인터페이스도 extends 키워드를 사용하여 인터페이스 간의 상속을 구현할 수 있으며 다중 상속도 표현할 수 있다.

 

 

인터페이스 예제

package interface_;

public interface RemoteController {
	
	// 최대 배터리량, 최소 배터리량을 상수로 지정
	int MAX_BATTERY = 100;
	int MIN_BATTER = 0;
	
	// 리모콘이 가져야 하는 필수 기능에 대해 정의
	public void turnOn();
	public void turnOff();
	public void showStatus();

}
package interface_;

public class TVRemoteController implements RemoteController {

	private final int inch;
	private int channel;
	
	public TVRemoteController(int inch) {
		this.inch = inch;
		this.channel = 1;
	}
	
	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다.");
		
	}

	@Override
	public void turnOff() {
		System.out.println("TV를 끕니다.");
		
	}

	@Override
	public void showStatus() {
		System.out.println("화면크기: " + this.inch);
		System.out.println("현재채널: " + this.channel);
		
	}
	
	public void setChanneldown() {
		// 1번까지만 채널이 있다.
		if(this.channel - 1 < 1) {
			this.channel = 1;
		} else {
			this.channel--;
		}
	}
	
	public void setChannelup() {
		this.channel++;
	}

}
package interface_;

public class RobotCleanerRemoteController implements RemoteController {

	public String modelName;
	public String price;
	
	// 로봇청소기 생성자
	public RobotCleanerRemoteController(String modelName, String price) {
		this.modelName = modelName;
		this.price = price;
	}
	
	@Override
	public void turnOn() {
		System.out.println("로봇청소기를 킨다.");
		
	}

	@Override
	public void turnOff() {
		System.out.println("로봇청소기를 끈다.");
		
	}

	@Override
	public void showStatus() {
		System.out.println("모델명: " + this.modelName);
		System.out.println("가격: " + this.price);
		
	}

}
package interface_;

public class MainClass {

	public static void main(String[] args) {
		// 인터페이스 역시 구현체를 다형성 형식으로 받을 수 있다.
		RemoteController rc = new TVRemoteController(50);
//		RemoteController rc = new RobotCleanerRemoteController("imou", "28000");
		
		rc.turnOn();
		rc.showStatus();
		rc.turnOff();
		
	}
}

 

 

 

부록 : has - a 관계

상속의 전제는 is - a 관계다.

 

그런데 현실에서는 is - a 관계가 아님에도 불구하고 다른 객체의 기능을 쓸 수 있는 경우가 많다.

 

이때 has - a 관계로 설정해 객체간 사용을 구현하게 된다.

 

경찰 객체와 총 객체가 있을때 총을 경찰이 발사하는 상황을 구현한다면

경찰이 멤버변수로 총을 보유하고 있는 상황을 구현해야지,

경찰이 총의 기능을 상속해서는 안된다는 뜻이다.

 

예제

package has_a;

public class Gun {
	private int bullet; // 총알 갯수
	private String modelName; // 총기 모델명
	private String gunNumber; // 총번
	
	public Gun(String modelName, String gunNumber) {
		this.bullet = 5;
		this.modelName = modelName;
		this.gunNumber = gunNumber;
	}
	
	public void shoot() {
		if(this.bullet > 0) {
			this.bullet--;
			System.out.println("총을 쐈습니다.");
		} else {
			System.out.println("방아쇠를 당겼지만 총알이 없습니다.");
		}
	}

	public void reload() {
		this.bullet = 5;
	}
}
package has_a;

public class Police {
	// 상속 없이 Gun 기능을 사용하기 위해 멤버변수 Gun도 가진다.
	private Gun gun;
	private String name;
	private int height;
	
	public Police(Gun gun, String name, int height) {
		this.gun = gun;
		this.name = name;
		this.height = height;
	}
	
	public void shoot() {
		this.gun.shoot();
	}
	
	public void showStatus() {
		System.out.println("소유 총기: " + this.gun);
		System.out.println("이름: " + this.name);
		System.out.println("키 : " + this.height);
	}

}
package has_a;

public class MainClass {

	public static void main(String[] args) {
		// Gun을 new 키워드로 생성해야 Police 생성자에 전달 가능
		Gun gun = new Gun("M-16", "369486");
		
		// gun을 사전에 생성하지 않으면 넘길 방법이 없다.
		Police police = new Police(gun, "나경찰", 180);
		
		police.shoot();
		police.shoot();
		police.shoot();
		police.shoot();
		police.shoot();
		police.shoot();
		police.shoot();
		police.shoot();
		police.shoot();

	}

}