ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 접근제어자
    언어/Java 2024. 11. 8. 21:37

    접근 제어자 이해 1

    접근 제어자란?

    • 해당 클래스를 외부에서 특정 필드나 메서드에 접근하는 것을 허용하거나 제한할 수 있다.
    • Java 에서 public 이나 private
    package access;
    
    public class Speaker {
        int volume;
    
        Speaker(int volume) {
            this.volume = volume;
        }
    
        void volumeUp() {
            if (volume >= 100) {
                System.out.println("음량을 증가할 수 없습니다. 최대 음럄입니다.");
            } else {
                volume += 10;
                System.out.println("음량을 10 증가합니다.");
            }
        }
    
        void volumeDown() {
            volume -= 10;
            System.out.println("voulumeDown 호출");
        }
    
        void showVolume() {
            System.out.println("현재 음량: " + volume);
        }
    }
    
    package access;
    
    public class SpeakerMain {
        public static void main(String[] args) {
            // 생성자를 통해서 볼륨의 초기값을 90으로 세팅해줌
            Speaker speaker = new Speaker((90));
    
            speaker.showVolume();
    
            speaker.volumeUp();
            speaker.showVolume();
    
            speaker.volumeUp();
    
            // 필드에 직접 접근 TDA (Tell Dont Ask 에서 asking을 한 경우
            System.out.println("volume 필드에 직접 접근 수정");
            speaker.volume = 200;
    
            speaker.showVolume(); // 200, 스피커 과부하 걸려서 폭팔 ㅅㄱ. 즉, 직접 접근해버림
    
            // 직접 막는 게 소용이 없다. 왜? 작접 접근해서 바꿔버리면 오또칼껀뎁
    
        }
    }
    
    • 직접 접근해서 값을 바꾸면 스피커는 폭발한다.
    • 그러면 직접 접근을 제어할 무언가가 필요로 하다 !!! (근본적인 해결 방법)

    접근 제어자 이해 2

    package access;
    
    public class Speaker {
        private int volume;
        . . .
    }
    
    • private 접근 제어자는 모든 외부 호출을 막는다. 따라서 private 이 붙은 경우 해당 클래스 내부에서만 호출할 수 있다.
    • 따라서 외부에서 접근하려고 하면 아래 사진과 같이 막아준다. (컴파일 오류, 는 좋은 것이다.)

    <aside> 💡

    좋은 프로그램은 무한한 자유도가 주어지는 프로그램이 아니라 적절한 제약을 제공하는 프로그램이다.

    </aside>

    접근 제어자의 종류

    • private : 모든 외부 호출을 막는다.
      • 클래스 안으로 속성과 기능을 숨길 때 사용, 외부 클래스에서 해당 기능을 호출할 수 없다.
    • default (package-private) : 같은 패키지 안에서 호출은 허용한다.
      • 패키지 안으로 속성과 기능을 숨길 때 사용, 외부 패키지에서 해당 기능을 호출할 수 없다.
      • 패키지-프라이빗이 더 정확한 표현이다.
      • 만약 접근 제어자를 안쓰면 패키지-프라이빗이 적용되기 때문에 디폴트라고 붙여졌다.
    • protected : 같은 패키지 안에서 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.
      • 상속 관계로 속성과 기능을 숨길 때 사용, 상속 관계가 아닌 곳에서 해당 기능을 사용할 수 없다.
    • public : 모든 외부 호출을 허용한다.
      • 기능을 숨기지 않고 어디서든 호출할 수 있게 공개한다.

    위에서 아래로 가장 많이 차단하고 가장 많이 허용하는 순서이다.

    접근 제어자 사용 - 필드, 메서드

    package access.a;
    
    public class AccessData {
        public int publicField;
        int defaultField;
        private int privateField;
    
        public void publicMethod() {
            System.out.println("publicMethod 호출 " + publicField);
        }
    
        void defaultMethod() {
            System.out.println("defaultMethod 호출" + defaultField);
        }
    
        private void privateMethod() {
            System.out.println("privateMethod 호출 " + privateField);
        }
    
        public void innnerAccess() {
            System.out.println("내부 호출");
            publicField = 100;
            defaultField = 200;
            privateField = 300;
            publicMethod();
            defaultMethod();
            privateMethod();
        }
    
    }
    
    package access.a;
    
    public class AccessinnerMain {
        public static void main(String[] args) {
            AccessData data = new AccessData();
    
            // public 호출 가능
            data.publicField = 1;
            data.publicMethod();
    
            // 같은 패키지 default 호출 가능
            data.defaultField = 2;
            data.defaultMethod();
    
            // private 호출 불가
    //        data.privateField = 3;
    //        data.privateMethod();
    
            // innerAccess는 public 이라 호출이 가능하다.
            data.innnerAccess();
    
        }
    }
    
    • innerAccess 는 public 이기 때문에 외부에서 호출할 수 있다. 그리고 innerAccess 는 내부로 들어와서 private등을 호출이 가능한 것이다. 같은 클래스 안에 있으니깐 !

    [다른 패키지 일 때]

    package access.b;
    
    import access.a.AccessData;
    
    public class AccessOuterMain {
        public static void main(String[] args) {
            AccessData data = new AccessData();
    
            // public 호출 가능
            data.publicField = 1;
            data.publicMethod();
    
            // 다른 패키지 default 호출 불가
            // data.defaultField = 2;
            // data.defaultMethod();
    
            // private 호출 불가
    //        data.privateField = 3;
    //        data.privateMethod();
    
            // innerAccess는 public 이라 호출이 가능하다.
            data.innnerAccess();
        }
    }
    

    접근 제어자 사용 - 클래스 레벨

    클래스 레벨의 접근 제어자 규칙

    // 가능
    public class Speaker {
    . . .
    }
    
    class Speaker {
    . . .
    }
    
    // 불가능
    private class Speaker {
    . . .
    }
    
    protected class Speaker {
    . . .
    }
    
    • 클래스 레벨의 접근 제어자는 public , default 만 사용 가능하다.
    public class Speaker {
    . . .
    }
    
    ****class Speaker1 [
    ...
    }
    
    class Speaker2 [
    ...
    }
    
    ****class Speaker3 [
    ...
    }
    
    • public 클래스는 반드시 파일명과 같아야 한다.
      • 하나의 자바 파일에 public 클래스는 하나만 등장해야 한다.
      • 하나의 자바 파일에는 default 접근 제어자를 사용하는 클래스는 무한정 만들 수 있다.

    캡슐화

    1. 데이터를 숨겨라
      • 객체에는 속성(데이터)과 기능(메서드)를 숨겨라. 특히 속성(데이터)
      → 단지 기능을 사용할 뿐이다. 자동차 속도를 뜯어서 빠르게 하지 않지 않느냐객체의 데이터는 객체가 제공하는 기능인 메서드를 통해서 접근해야 한다.
    2. </aside>
    3. <aside> 💡
    4. 기능을 숨겨라
      • 외부에서 사용하지 않고, 내부에서만 사용하는 기능들을 숨겨라 !!!

    <aside> 💡

    정리

    데이터는 모두 숨기고, 기능은 꼭 필요한 기능만 노출하는 것이 좋은 캡슐화이다.

    </aside>

    package access.b;
    
    public class BankAccount {
        private int balnce;
    
        public BankAccount() {
            balnce = 0;
        }
    
        // public 메서드: deposit
        public void deposit(int amoubt) {
            if (isAmountValid(amoubt)) {
                balnce += amoubt;
            } else {
                System.out.println("유효하지 않는 금액입니다");
            } 
        }
    
        // public 메서드: withdraw
        public void withdraw(int amount) {
            if (isAmountValid(amount) && balnce - amount >= 0) {
                balnce -= amount;
            } else {
                System.out.println("유효하지 않는 금액이거나, 잔액이 부족합니다.");
            }
        }
    
        // public 메서드: getBlance
        public int getBlance() {
            return balnce;
        }
    
        // 내부에서만 쓰는 기능이기 때문에 private, 즉 같은 클래스에서만 사용 가능하게끔 만들었다.
        private boolean isAmountValid(int amount) {
            return amount > 0;
        }
    }
    
    • 만약에 isAmountVaild 메서드가 public 이라면 기능을 쓰는 사람 입장에서 검증을 해야 되나 고민하게 만들 수도 있다.
    package access.b;
    
    public class BankAccountMain {
        public static void main(String[] args) {
            BankAccount account = new BankAccount();
            account.deposit(10000);
            account.withdraw(3000);
            System.out.println("balance = " + account.getBlance());
        }
    }
    

    [결론]

    • 개발자 입장에서는 사용할 수 있는 메서드가 늘었다. 그리고 직접 사용해도 된다고 생각할 수도 있다. 왜냐면 외부에 공개하는 것은 그것을 외부에서 사용해도 된다는 뜻이기 때문이다. 결국 모든 검증과 캠슐화가 깨지고 잔고를 무한정 늘리고 출금하는 심각한 문제가 발생할 수 있다.

    <aside> 💡

    접근 제어자와 캡슐화를 통해 데이터를 안전하게 보호하는 것은 물론이고, BankAmount 를 사용하는 개발자 입장에서 해당 기능을 사용하는 복잡도를 낮출 수 있다.

    </aside>


    <aside> 💡

    단축키

    alt + insert

    생성자 만들어준다.

    </aside>


    문제와 풀이

    package access.ex;
    
    public class MaxCounter {
        // private 로 했는 데 밖에서 넣어줄 수 있나?
        private int counter;
        private int max;
    
        // 생성자, main에서 초기 counter 값을 지정함.
        // MaxCounter 은 default로 되어 있기 때문에 생성자를 같은 패키지 안에서 사용 가능하다.
        // 그러면 이걸 바꾸면?
        // private로 하면 밖에서 오류난다. 오우오우
        MaxCounter(int counter) {
            this.counter = counter;
            this.max = 6;
        }
    
        // 다른 패키지에서 사용할 수 있게 public
        public void increment() {
            if (isCounterValid(counter)) {
                // 매개변수와 멤버변수의 변수명이 동일하기 때문에 this 키워드를 사용한다.
                this.counter += 1;
            } else {
                System.out.println("최대값을 초과할 수 없습니다");
            }
        }
    
        // 다른 패키지에서 사용할 수 있게  public
        public int getCount() {
            return counter;
        }
    
        private boolean isCounterValid(int counter) {
            return counter + 1 < max ;
        }
    }
    
    package access.ex;
    
    public class CounterMain {
        public static void main(String[] args) {
            // private int counter; 로 해서 밖에서 될까? 고민했었는 데 된다.
            // 왜 될까? MaxCounter 생성자를 default로 선언했기 때문이다.
            // 따라서 private 로 선언하게 된다면 여기서는 컴파일 오류가 난다.
            public MaxCounter counter = new MaxCounter(3);
            counter.increment();
            counter.increment();
            counter.increment();
            counter.increment();
    
            int count = counter.getCount();
            System.out.println(count);
    
        }
    }
    
    

    [헷갈렸던 부분]

    1. 생성자 초기화할 때 어떻게 하는 지
    2. 메서드 타입을 void 할 지 int 로 할 지
      1. 반환해야 되나 말아야 되나 고민하다가 하면서 로직을 수정함.
    3. max 를 private 로 할 지
      1. max 값을 밖에서 정해주나 아니냐 몰라서 일단 생성자 초기화할 때 임의로 6 이라는 값을 넣어두었음.
      2. 캡슐화의 원칙에서 데이터를 숨겨라 해서 숨겨버림

    [배운 부분]

    1. 생성자도 public , private 접근 제어자를 사용할 수 있다.

    [출제자의 의도와 검증/실행 로직]

    package access.ex;
    
    public class MaxCounter {
        private int count = 0; // 여기서는 0을 넣지 않아도 int는 0으로 초기화됨.
        private int max;
        
        // 출제자의 의도는 다름.
        public MaxCounter(int max) {
            this.max = max; // 최댓값을 설정해주고 싶었던 거임...
        }
    
        // 리팩토링
        public void increment() {
            // 검증 로직
            if (count >= max) {
                System.out.println("최댓값을 초과할 수 없습니다.");
                return;
            }
    
            // 실행 로직
            count ++;
            /*
            * 검증 로직 / 실행 로직으로 나누면 명확하게 검증이 되서 좋다. if-else 를 사용할 수도 있지만,
            * */
        }
    
        public int getCount() {
            return count;
        }
    }
    
    

    문제 - 쇼핑 카트

    <aside> 💡

    단축키

    Ctrl + Alt + v

    item[i];

    위에 쓰면 Item item = 자동으로 만들어줌.

    </aside>

Designed by Tistory.