Generic한 리스트 클래스를 만들어보자.

 

리스트

여러 개의 데이터를 저장하고

임의의 위치에 새로운 데이터를 추가하거나

데이터의 삭제가 가능하고

임의의 위치의 데이터를 읽을 수 있고,

용량에 제한이 없는 

...

클래스 

 


 

사용 예

예를 들어 String들을 저장하려면

 

MyArrayList<String> myList = new MyArrayList<String>();

 

생성된 리스트에 String 추가

 

myList.add("Bashful");

myList.add("Awful");

myList.add("Jumpy");

 

추가한 순서대로 저장된다. 

 

myList.add(2, "Doc");

2번위치에 (배열의3번째칸) 에 추가하라 기존에있던 데이터를 한칸 씩 뒤로 밈

 

myList.remove(1);

1번위치 값을 삭제하고 빈 공간을 앞으로 땡겨온다.

 

myList.set(2,"Sneezy");

2번위치를 덮어쓰는 기능.

 

String dwarf = myList.get(2)

인덱스를 매개변수로 값을 리턴하는기능

 

int index = myList.indexOf("Sneezy");

인덱스 값을 리턴해준다.

없다면 -1 리턴

 

package section5;

import java.util.Arrays;

public class MyArrayList<T> {

	private static final int INIT_CAPACITY = 10;
	private T[] theData;
	private int size;
	private int capacity = INIT_CAPACITY;
	
	public MyArrayList() {
		theData = (T []) new Object [INIT_CAPACITY];
		size = 0;
	}
	
	public void add(int index, T anEntry) {
		if(index < 0 || index > size) {
			throw new ArrayIndexOutOfBoundsException(index);
			}
		if(size >= capacity){
			reallocate();
			}
		for(int i=size-1; i>=index; i--) {
			theData[i+1] = theData[i];
		}
		theData[index] =anEntry;
		size++;
	}
	
	private void reallocate() {
		capacity *= 2;
		theData = Arrays.copyOf(theData, capacity);
	}

	public void add(T anEntry) {
		add(size, anEntry);
	}
	public int size() {
		return size;
	}
	
	public int indexOf(T anEntry) {
		for(int i =0; i<size; i++) {
			if(theData[i].equals(anEntry)) {
				return i;
			}
		}
		return -1;
	}
	public T get(int index) {
		if(index <0 || index >= size) {
			throw new ArrayIndexOutOfBoundsException(index);
		}
		return theData[index];
	}
	
	public T set(int index, T newValue) {
		if(index <0 || index >= size) {
			throw new ArrayIndexOutOfBoundsException(index);
		}
		T oldValue = theData[index];
		theData[index] = newValue;
		return oldValue;
	}
	
	public T remove(int index) {
		if(index < 0|| index >= size) {
			throw new ArrayIndexOutOfBoundsException(index);
		}
		T returnValue = theData[index];
		for(int i = index+1; i<size; i++) {
			theData[i-1]=theData[i];
		}
		size--;
		return returnValue;
	}
	
	
	
}

 

Generic Programming

 

제네릭 프로그래밍은 데이터 형식에 의존하지 않고 하나의 값이 여러

다른 데이터 타입들을 가질 수 있는 기술에 중점을 두어 재사용성을 높일 수 있는

프로그래밍 방식이다.

 

Generic한 변수/자료구조

Event ev;

Event []events = new Event[capacity];

Object obj;

 

Generic한 알고리즘(method)

Arrays.sort(shpes,0,n);

 

Generic한 클래스

Generics


예)

public class Box<T>{

  private T t;

  public void set(T t) {this.t = t;}

  public T get() { return t;}

}

 

Box<Integer>Box = new Box<Integer>();

box.set(new Integer(10));

Integer a = box.get():

새로운 할인 정책을 확장해보았다.

 

개발도중 기획자에 의해서 고정할인 금액이 아니라

주문 금액당 할인하는 정률할인으로 변경하고 싶다고 요구하였다.

 

새로운 정률할인 정책을 추가하였다.

 

package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;

public class RateDiscountPolicy implements DiscountPolicy{

    private int discountPercent = 10;

    @Override
    public int discount(Member member, int price) {
        if(member.getGrade() == Grade.VIP){
            return price * discountPercent / 100;
        } else{
            return 0;
        }
    }
}

DiscountPolicy를  implements 한 RateDiscountPolicy 클래스를 만들고

VIP라면 10% 할인한 할인가를 리턴한다.

 

package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class RateDiscountPolicyTest {

    RateDiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName("VIP는 10% 할인이 적용되어야 한다.")
    void vip_o() {
        //given
       Member member = new Member(1L,"memberVIP", Grade.VIP);
        //when
        int discount = discountPolicy.discount(member, 10000);

        //then
        Assertions.assertThat(discount).isEqualTo(1000);
    }

    @Test
    @DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
    void vip_x() {
        //given
        Member member = new Member(2L,"memberVIP", Grade.BASIC);
        //when
        int discount = discountPolicy.discount(member, 10000);

        //then
        Assertions.assertThat(discount).isEqualTo(1000);
    }

}

Junit을 이용해 역시 테스트 케이스들을 작성해보았다. 

vip_o()메서드는 vip일때 잘 동작하는지 확인하고

vip_x()메서드는 vip가 아니라면 어떻게 되는지 확인하는 테스트이다.

@DisplayName 을 쓰면 한글을 적을 수 있다.! Junit5부터 가능!

 

vip_x() 메서드에 1000원을 비교해보니 

기대값은 1000원이지만 

실제는 0이었다라고 확인할 수 있었다.

이번 시간에는 잘 작성되었는지 테스트 하는 과정을 정리하였다!

 

 package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;

public class OrderApp {
    public static void main(String[] args) {

        MemberService memberService = new MemberServiceImpl();
        OrderService orderService = new OrderServiceImpl();

        Long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "itemA",10000);

        System.out.println("order = "+ order);
        System.out.println("order.calcuatePrice = " + order.calculatePrice());
    }
}

 

먼저 멤버서비스와 주문서비스를 생성하고

멤버객체를 생성할때 1L, memberA , VIP

값을 넘겨서 서비스의 회원가입로직을 실행하고 

 

주문객체를 생설할때 createOrder 메서드를 이용하여 아이디, 상품명, 상품각격을 넘겨 주문을 생성한다.

 

그리고 order객체 자체를 출력하면  toString()메서드가 실행된다.

할인된 가격을 보기위해서 Order의 calculatePrice메서드를 실행하고 확인해보았다.

 

정상적으로 짜여져있는걸 확인할 수 있었다.

 

눈으로 직접확인하는 출력문 테스트는 이제 의미가없다

Junit을 이용해보자!

 

package hello.core.order;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public class OrderServiceTest {

    MemberService memberService = new MemberServiceImpl();
    OrderService orderService = new OrderServiceImpl();

    @Test
    void createOrder(){
        long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "itemA",10000);
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}

<test 디렉터리 하위>

전반적인 코드는 동일하지만

assertj 라이브러리를 이용한

assertThat()메서드를 이용하여 값을 확인한다.

이번 시간에는 주문과 할인 도메인을 개발해 보았다.

package hello.core.discount;

import hello.core.member.Member;

public interface DiscountPolicy {

    int discount(Member member, int price);
}

discount/DiscountPolicy 인터페이스를 생성하였다.

멤버타입 객체와 가격정보를 넘기는 discount 메서드 명시!

할인된 금액을 리턴한다.

 

package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;

public class FixDiscountPolicy implements DiscountPolicy{

    private int discountFixAmount = 1000;

    @Override
    public int discount(Member member, int price) {
        if(member.getGrade() == Grade.VIP){
            return discountFixAmount;
        }
        else {
            return 0;
        }
    }
}

DiscountPolicy를 구현한 구현체 FixDiscountPolicy를 만들었다.

 

1000원을 할인 하기로하였다.

(등급이 VIP일시)

if문에 == 이 들어갈 수 있는 이유는 Grade가 enum이기 때문이다.

회원등급이 VIP라면 1000원을 리턴하고

그렇지 않다면 0을 리턴한다.

 

package hello.core.order;

public class Order {

    private Long memberId;
    private String itemName;
    private int itemPrice;
    private  int discountPrice;

    public Order(Long memberId, String itemName, int itemPrice, int discountPrice) {
        this.memberId = memberId;
        this.itemName = itemName;
        this.itemPrice = itemPrice;
        this.discountPrice = discountPrice;
    }

    public int calculatePrice(){
        return itemPrice - discountPrice;
    }


    public Long getMemberId() {
        return memberId;
    }

    public void setMemberId(Long memberId) {
        this.memberId = memberId;
    }

    public String getItemName() {
        return itemName;
    }

    public void setItemName(String itemName) {
        this.itemName = itemName;
    }

    public int getItemPrice() {
        return itemPrice;
    }

    public void setItemPrice(int itemPrice) {
        this.itemPrice = itemPrice;
    }

    public int getDiscountPrice() {
        return discountPrice;
    }

    public void setDiscountPrice(int discountPrice) {
        this.discountPrice = discountPrice;
    }

    @Override
    public String toString() {
        return "Order{" +
                "memberId=" + memberId +
                ", itemName='" + itemName + '\'' +
                ", itemPrice=" + itemPrice +
                ", discountPrice=" + discountPrice +
                '}';
    }
}

order/Order  클래스를 만들었다.

 

멤버아이디, 상품명, 상품가격, 할인가를 가지고 있다.

 

그리고 계산된 결과를 반환하는 calculatePrice메서드를 만들었다.

그리고 toString메서드를 오버라이딩하여 나중에 객체 자체를

출력할때 toString이 호출되도록 만들었다.

 

package hello.core.order;

public interface OrderService {
    Order createOrder(Long memberId, String itemName, int itemPrice);
}

order/OrderService 인터페이스를 만들었다.

 

 createOrder 메서드를 만드는데 멤버아이디, 상품명, 상품가격을 넘기면

최종 결과를 반환하는 메서드이다.

 

package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

그리고 대망의 핵심 OrderService 인터페이스를 구현한 OrderServiceImpl을 만들었다.

 

멤버저장소에서 회원을 찾아야하고, 할인 정책을 필요로하기에 

객체를 생성한다.

 

createOrder를 구현하기 위해 

member 객체에서 아이디값을 넘겨서 객체를 받아오고

discountPrice 변수에  discountPolicy의 discount함수를 이용하여 멤버객체와, 상품가격을 넘긴다.

 

단일 책임 원칙을 잘 설계한 예제이다.

이번 시간에는 주문과 할인 도메인을 설계 해보았다.

 

주문과 할인 정책

 

회원은 상품을 주문할 수 있다.

회원 등급에 따라 할인 정책을 적용할 수 있다.

할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라.(나중에 변경 가능)

할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈

직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도있다. (미확정)

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55338?tab=no

 

1. 주문생성 : 클라이언트는 주문 서비스에 주문 생성을 요청

2. 회원조회 : 할인을 위해서는 회원 등급이 필요하다. 그래서 주문 서비스는 회원 저장소에서 회원을 조회한다.

3. 할인 적용 : 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책을 위임한다.

4. 주문 결과 반환 : 주문 서비스는 할인 결과를 포함한 주문 결과를 반환한다.

 

예제를 간단하게 하기위해 상품은 따로 만들지 않았다.

DB에 저장하지 않고 단순히 주문 결과를 반환하는 예제이다.

 

 

                        주문 도메인 전체

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55338?tab=note&speed=0.75

역할과 구현까지 표현한 것이 이 그림이다.

역할 먼저 만들고 구현을 다음에 만들었기때문에 자유롭게 구현 객체를 조립할 수 있게 설계했다.

덕분에 회원 저장소는 물론이고, 할인 정책도 유연하게 변경할 수 있다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55338?tab=note&speed=0.75

클래스 다이어그램으로 표현하면 OrderService 인터페이스를 OrderServiceImpl로 구현체를 만들고 

나머지 인터페이스들도 구현체들을 구현한 모습이다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55338?tab=note&speed=0.75

new 명령어를 통해 생성하여 동적으로 객체들의 연관관계가 맺어지는 그림이다.

 

클라이언트가 주문 서비스 구현체를 new로 호출하고 new메모리 회원 저장소 new정액 할인 정책을 하는 것!

회원을 메모리에서 조회하고, 정액할인 정책을 지원해도 주문 서비스를 변경하지않아도 된다.

 

역할들의 협력 관계를 그대로 재사용 할 수 있다. 

메모리 저장소, 정액 할인 정책이 변경되어도 주문 서비스 구현체를 변경 할 필요가 없다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55338?tab=note&speed=0.75

회원을 메모리가 아닌 실제 DB에서 조회하고, 정률 할인 정책을 지원해도 주문 서비스를 변경하지 않아도 된다.!!!

협력관계를 재사용 할 수 있다.

정렬하는 기능을 추가하였다.

	private void handleSort() {
		for(int i =n-1;i>0;i--) {
			for(int j=0;j<i;j++) {
				if(Shapes[j].computearea() > Shapes[j+1].computearea()) {
					Shape tmp = Shapes[j];
					Shapes[j] = Shapes[j+1];
					Shapes[j+1] = tmp;
					
				}
			}
		}
		
	}

 

같은 알고리즘을 데이터의 타입이 다르다는 이유로 반복적으로 타이핑하는것은 불만스럽다는

생각을 하게되었다.

 

그런것을 generic하지 않다라고 말을한다.

그 의미는 Shape타입 데이터만 가능한 메서드이다.

 

어떠한 데이터가 들어오든 가능하게 하는것을 

generic하다 라고 말을한다.

 

package chapter5;

public interface MyComparable {
	public int compareTo(Object o);
}

MyComparable 인터페이스 생성!

 

package chapter5;

public abstract class Shape implements MyComparable{
	public  String shapeName;
	
	public Shape(String name) {
		shapeName =name;
	}
	
	public abstract double computearea();
	public abstract double computePerimeter();
	
	public int compareTo(Object o) {
		double myArea = computearea();
		double yourArea = ((Shape)o).computearea();
		if(myArea < yourArea)
			return -1;
		else if(myArea == yourArea)
			return 0;
		else
			return 1;
	}

}

compareTo 메서드 구현

 

	private void handleSort(MyComparable[] data, int size) {
		for(int i =size-1; i>0; i--) {
			for(int j=0; j<i; j++) {
				if(Shapes[j].compareTo(Shapes[j+1])>0) {
					MyComparable tmp = data[j];
					data[j] =data[j+1];
					data[j+1] = tmp;
				}
			}
		}
	}

정렬 알고리즘을 Gereric하게 바꾸어보았다!

이제는 모든 타입의 데이터를 (MyComparable 인터페이스를 implements하고 메서드를 구현하기만 한다면)

사용 가능하다.

 

자바 API가 제공해주는 Arrays.sort 를 쓰기만 하면 sort를 구현할 필요도 없다.

Comparable 인터페이스 또한 원래 있는 인터페이스이다.

 

Interface vs abstract class

 

추상 메서드로만 구성된 추상 클래스는 인터페이스와 완전히 동일한가?

 

다중 상속

자바에서는 다중 상속을 허용하지 않는다.

하지만 하나의 클래스가 여러개의 Interface를 implement하는 것은 가능

 

인터페이스

 

추상 메서드만을 가진 순수한 추상 클래스

static final 데이터 멤버를 가질 수 있음.

추상메서드의 극단적 형태라고 생각하면 이해하기 쉽다!

 


Shape 프로그램

 

사각형, 원, 직각삼각형 등의 도형들을 입력받아 저장하고

면적과 둘레 길이를 계산하는 기능

 

실행 예

$ add R 1 2

$ add C 5

$ show // 정보출력

 1. Rectangle : width is 1, height is 2

 2. Circle : radius is 5

$showdetail // 면적 높이 계산 결과 출력

 1. Rectangle : width is 1, height is 2

   -The area is 2.0

   - The perimeter is 6.0

 2. Circle : radius is 5

   - The area is 78.539816339744683

   - The perimeter is 31.41592653589793

 

$ add R 2 6

$ sort //면적에 대해서 오름차순으로 정렬

$ exit //종료

 


package chapter5;

public abstract class Shape {
	public  String shapeName;
	
	public Shape(String name) {
		shapeName =name;
	}
	
	public abstract double computearea();
	public abstract double computePerimeter();

}

Shape클래스 각 도형의 부모 추상클래스

 

package chapter5;

public class Rectangle extends Shape{
	public int width;
	public int height;
	
	public Rectangle(int w, int h) {
		super("Rectangle");
		width = w;
		height = h;
	}
	
	public double computearea() {
		return (double)width*height;
	}
	
	public double computePerimeter() {
		return 2.0*(width+height);
	}
	
	public String toString() {
		return "Rectangle: width is " + width + ", height is " + height;
	}

	
	
}

사각형 클래스

 

package chapter5;

public class Circle extends Shape{
	public int radius;
	
	public Circle(int r) {
		super("Circle");
		radius = r;
	}
	
	public double computearea() {
		return Math.PI * radius * radius;
	}
	
	public double computePerimeter() {
		return 2.0*Math.PI*radius;
	}
	
	public String toString() {
		return "Circle: radius is" + radius;
	}
	
}

원 클래스

 

package chapter5;

import java.util.Scanner;

public class ShapeApplication {
	
	private int capacity = 10;
	private Shape[] Shapes = new Shape[capacity];
	private int n=0;
	private Scanner kb = new Scanner(System.in);
	
	public void processCommand() {
		while(true) {
			System.out.print("$ ");
			String command = kb.next();
			if(command.equals("add")) {
				handleAdd();
			}
			else if(command.equals("show")||command.equals("showdetail")){
				handleShow(command.equals("showdetail"));
			}
			else if(command.equals("sort")) {
				handleSort();
			}
			else if(command.equals("exit")) {
				break;
			}
		}
		
	}
	private void handleSort() {
		// TODO Auto-generated method stub
		
	}
	private void handleShow(boolean detailed) {
		for(int i =0; i<n; i++) {
			System.out.println((i+1)+". "+Shapes[i].toString());
			if(detailed) {
				System.out.println("      The area is "+ Shapes[i].computearea());
				System.out.println("      The perimeter is "+ Shapes[i].computePerimeter());
			}
		}
	}
	private void handleAdd() {
		String type = kb.next();
		switch(type) {
		
		case "R": 
			addShape(new Rectangle(kb.nextInt(),kb.nextInt()));
			break;
			
		case "C":
			addShape(new Circle(kb.nextInt()));
			break;
		case "T":
			//생략
		}
		
	}
	private void addShape(Shape shape) {
		if(n >= capacity)
			reallocate();
		Shapes[n++] = shape;
		
	}
	private void reallocate() {
		capacity *=2;
		Shape[] tmp = new Shape[capacity];
		System.arraycopy(Shapes, 0, tmp, 0, Shapes.length);
		Shapes = tmp;
	}
	public static void main(String[] args) {
		ShapeApplication app = new ShapeApplication();
		app.processCommand();
	}

}

메인 함수를 포함한 클래스 

 

 

아직 미완성 되었지만 지금까지 잘된 것을 확인할 수 있다.

추상 클래스

 

추상 메서드는 선언만 있고 구현이 없는 메서드

 

추상메서드를 포함한 클래스는 추상클래스

 

추상 메서드와 추상클래스는 키워드 abstract로 표시

 

추상 클래스는 객체를 만들 수 없음. 따라서 서브 클래스를

만드는 용도로 사용

 


 

예를들어 스케줄러 프로그램에서 Event클래스를 정의했지만

객체를 만든 적은 없다 Event배열은 만들었지만 Event객체는 아니다.

Event클래스를 만든 이유는 다형성을 사용하기 위한 목적으로

다른 특정이벤트 클래스들을 상속하게하기 위해서였다.

 

package chapter4;

public abstract class Event {
	public String title;
	
	public Event(String title) {
		this.title = title;
	}
	
	public abstract boolean isRelevnet(MyDate date);
}

그래서 추상화클래스로 만들고 추상화 메서드를 추가해 준뒤

각각 3가지 이벤트종류에 따라 코드를 재정의해주었다.

 

	private void handleshow() {
		String dateString = kb.next();
		MyDate theDate = parseDateString(dateString);
		for (int i=0; i<n;i++) {
			if(events[i].isRelevent(theDate))
				System.out.println(events[i].toString());
		}
	}

메인 클래스의 함수 

 

show 라는 명령어와 함께 날짜를 입력하면

걸치는 이벤트들을 출력해준다.

'알고리즘 with 자바 > 자료구조' 카테고리의 다른 글

추상클래스와 인터페이스 3  (0) 2021.07.07
추상클래스와 인터페이스 2  (0) 2021.07.07
클래스 object와 Wrapper 클래스  (0) 2021.07.06
스케줄러 프로그램  (0) 2021.07.01
상속 3  (0) 2021.06.30

이번 시간에는 지난 포스팅에 개발한 코드들을 테스트를 해보았다.

 

package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;

public class MemverApp {

    public static void main(String[] args) {
        MemberService memberService = new MemberServiceImpl();
        Member member = new Member(1L, "memberA", Grade.VIP);
        memberService.join(member);

        Member findMember = memberService.findMember(1L);
        System.out.println("new member = " + member.getName());
        System.out.println("find member = " + findMember.getName());
    }
}

hello.core 밑에 메인함수를 가지는 클래스를 생성하고

멤버객체를 1,memberA,VIP를 주고 생성한다(생성자를 통하여)

그리고 가입기능을 실행하고 

 

그리고 findMember 변수를 이용하여 아이디값이 1인 객체를 가리키게하고

결국 같은 객체를 가리키게 된다.

이것을 출력문을 통해 눈으로 확인 할 수 있다.

 

하지만 이렇게 직접 눈으로 다 확인할 수는 없는 노릇이기에

Junit이란 테스트 프레임워크를 사용해보자!

 

package hello.core.member;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public class MemberServiceTest {

    MemberService memberService = new MemberServiceImpl();

    @Test
    void join(){
        //given
        Member member = new Member(1L,"memberA",Grade.VIP);

        //when
        memberService.join(member);
        Member findMember = memberService.findMember(1L);

        //then
        Assertions.assertThat(member).isEqualTo(findMember);
    }
}

test/hello.core/member/MemberServiceTest를 생성해준다.

 

위의 코드랑 다른점은

검증하는 부분이다.

Assertions를 이용하여 member와 findMember를 비교하여 같은지 확인한다.

 

테스트에 성공하였다. 눈으로 확인하는거 보다 좋고 테스트가 터졌을때 오류를 확인할 수 있다.

 


현재 회원 도메인 설계의 문제점이 있다.

 

다른 저장소로 변경할 때 OCP원칙을 잘 준수할까?

DIP를 잘 지키고 있을까?

이게 결국 의존관계가 인터페이스 뿐만 아니라 구현까지 모두 의존하는 문제가 있다.

의존성 주입을 해줘야할 것같다!

구체적인 문제는 차차 고쳐가도록 할 것이다.!

+ Recent posts