일급 객체, 일급 컬렉션, 원시 값 포장

minisoo·2024년 3월 17일
0
post-thumbnail

일급 객체

아래 3가지 조건을 충족하면 일급 객체라고 할 수 있다

  1. 변수에 담을 수 있어야 한다
  2. 매개변수로 함수를 전달할 수 있어야 한다
  3. 객체의 리턴값으로 사용할 수 있어야 한다

변수에 담을 수 있어야 한다

🔸 자바 ➡️ 불가능

public class Test {
	public static void test1() {
    	System.out.println("test1");
    
    public static void main(String[] args) {
    	System.out.println("main");
        //Object function = test1(); -> 불가능
    }
}

🔹 파이썬 ➡️ 가능

def add(input1, input2):
	return input1 + input2
    
 copy_add = add
 print(copy_add)
 print(add)
 print(copy_add(1,2))


copy_add와 add가 같은 주소값을 가르킴

매개변수로 함수를 전달할 수 있어야 한다

🔸 자바 ➡️ 불가능

public class Test {
	public static void test1() {
    	System.out.println("test1");
    }
    public static void print(Object function) {
    	function(); //불가능
    }
    public static void main(String[] args) {
    	print((Object) test1); //불가능
    }
}

🔹 파이썬 ➡️ 가능

def add(a, b):
	return a + b
def main(func, a, b):
	print(func(a, b):
 
 
main(add, 10, 12) #22출력

객체의 리턴값으로 사용할 수 있어야 한다

🔸 자바 ➡️ 불가능
🔹 파이썬 ➡️ 가능

def test(name):        
    def test_inner():
        print(f"{name}!")
    return test_inner

func = test("Velog")
func() #Velog! 출력

✔️ 자바 8부터 람다를 지원하면서 함수를 일급 객체처럼 다울 수 있는 방법을 제공

➰ 변수 할당

import java.util.function.Function;

public class Test {
	public static void test1() {
    	System.out.println("test1");
    }
    
    public static void main(String[] args) {
    	System.out.println("main");
        //Object function = test1();
        
        //Function 인터페이스를 사용하여 람다 표현식을 변수에 할당
        Function<Integer, Integer> square = x -> x*x;
        int result = square.apply(5);
    }
    
}

➰ 매개변수로 함수 전달

import java.util.function.Consumer;

public class Main {
    public static void print(Consumer<String> c, String str) {
        c.accept(str);
    }

    public static void main(String[] args) {
        print((t) -> System.out.println(t) ,"Hello World");
    }
}

➰ 함수 리턴

import java.util.function.Function;

public class Test {
    public static Function<String, String> hello() {
        return (t) -> {
            System.out.println(t );
            return t;
        };
    }

    public static void main(String[] args) {
        Function<String, String> func = hello();
        func.apply("Hello World");
    }
}
//Hello World 출력

일급 컬렉션

Collection(List, Map..)을 Wrapping하면서 그 외 다른 멤버 변수가 없는 상태

## 변경 전
public class Market {
    private String name;
    private List<Item> items;
}
public class Item {
    private String name;
    private int price;
}

➰ 아래 상태로 변경하게 되면 Items 클래스가 일급컬렉션이 된다

## 변경 후 
public class Market {
    private String name;
    private Items items;
}

public class Items {
    private List<Item> items;
}

public class Item {
    private String name;
    private int price;
}

특징

〰️ 상태와 로직을 한곳에서 관리

import java.util.List;

public class Market2 {

    private String name;
    private List<Coffee> coffees;

    public Market2(String name, List<Coffee> coffees) {
        validate(coffees);
        this.name = name;
        this.coffees = coffees;
    }

    private void validate(List<Coffee> coffees) {
        if (coffees.size() >= 5) {
            throw new IllegalArgumentException("5개 이상의 커피메뉴를 등록할 수 없습니다.");
        }
    }
}

해당 코드에서 Market메뉴가 coffee하나 인 경우 복잡해 보이지 않지만 만약 메뉴가 여러개 추가되는 경우 해당 validation 메서드가 market 클래스에 추가되기 때문에 market 클래스이 역할이 무거워지고, 중복코드가 많아진다

import java.util.List;

public class Market {

    private String name;
    private Coffees coffees;
}

public class Coffees {
    private List<Coffee> coffees;

    public Coffees(List<Coffee> coffees) {
        this.coffees = coffees;
    }

    private void validate(List<Coffee> coffees) {
        if (coffees.size() >= 5) {
            throw new IllegalArgumentException("5개 이상의 커피메뉴를 등록할 수 없습니다.");
        }
    }
}

public class Coffee {
    private String name;
    private int price;
}

커피를 일급 컬렉션으로 만들어 검증은 커피 클래스 내부에서 수행할 수 있게된다

〰️ 불변성을 보장할 수 있다

✔️ 컬렉션의 값을 변경할 수 있는 메소드가 없는 컬렉션을 만들어서 불변성을 보장할 수 있다

➕ 자세히! 생성자와 필요한 값만 반환하는 메서드만 존재하는 경우!!
만약, 생성자로 받은 컬렉션 값을 그대로 반환하는 getter가 존재하게 되면

List<Item> items = new ArrayList<>();
items.add(new Item("커피", 1000));
Items item = new Items(items);
item.getItem.add(new Item("쿠키", 2000));

이런 상황에서 값이 변경될 수 있다
만약 컬렉션 값을 그대로 반환하는 getter가 필요한 경우
1. 생성할 때 주소값을 재할당 한다
2. getter내에 unmodifiableList 사용한다

원시 값 포장

원시 타입의 값(int, double, char 등)을 객체로 감싸는 것으로 원시 타입에 이름을 부여하고 해당 값에 대한 유효성 검사, 비즈니스 로직의 추가 등을 가능하게 하여 코드의 가독성과 유지보수성을 높인다

#변경 전
public class Market {
    private String menu;
    private int price;

    public Market(String menu, int price) {
        validateMenu(menu);
        validatePrice(price);
        this.menu = menu;
        this.price = price;
    }

    private void validateMenu(String menu) {
        if (menu.length() < 1) {
            throw new IllegalArgumentException("이름은 한 글자 이상이어야 합니다.");
        }
    }

    private void validatePrice(int price) {
        if (price < 100) {
            throw new IllegalArgumentException("가격은 1원 이상이여야 합니다.");
        }
    }
}

해당 소스를 보면 마켓 클래스에서 메뉴, 가격에 대한 상태관리도 책임지고 있다
두 컬럼이여서 복잡해보이지 않지만 만약 컬럼이 5개만 넘어가도 마켓 클래스가 거대해질 수 있다

#변경 후 
public class Market {
    private Menu menu;
    private Price price;

    public Market(String menu, int price) {
        this.menu = new Menu(menu);
        this.price = new Price(price);
    }

}

public class Menu {
    private String menu;

    public Menu(String menu) {
        if (menu.length() < 1) {
            throw new IllegalArgumentException("이름은 한 글자 이상이어야 합니다.");
        }
        this.menu = menu;
    }
}

public class Price {
    private int price;

    public Price(int price) {
        if (price < 100) {
            throw new IllegalArgumentException("가격은 1원 이상이여야 합니다.");
        }
        this.price = price;
    }
}

메뉴와 가격에 대한 유효성 검사, 상태값을 스스로 관리할 수 있게되어 책임이 명확해지게 된다

유지보수성 측면에서
카페, 빵집, 음식점 클래스를 만든다고 가정했을 때
각각 클래스는 모두 '가격'이라는 데이터가 필요하게 된다

각 클래스에서 원시값 price를 사용하는게 아닌
원시값을 포장하여 Price 객체를 사용하게 되면 추후 가격에 대한 추가적인 로직이 필요한 경우 Price 클래스에 메소드를 추가함으로써 새로운 기능을 쉽게 확장할 수 있다


일급 객체
1급 객체
파이썬 - 일급 객체
일급 컬렉션을 사용하는 이유
원시 타입을 포장해야 하는 이유

profile
코딩하는 돌멩이 👻

0개의 댓글