├── 2장-학습 ├── 2절-최소한_뭔가는_캡슐화하세요 │ ├── empty.txt │ ├── cu.md │ ├── wedge.md │ ├── ryan.md │ ├── nabom.md │ ├── neozal.md │ ├── wannte.md │ ├── bepoz.md │ ├── pomo.md │ ├── suri.md │ ├── koda.md │ ├── gump.md │ ├── danyee.md │ └── joanne.md ├── 6절-불변-객체로-만드세요 │ ├── empty.md │ ├── ryan.md │ ├── wedge.md │ ├── suri.md │ ├── bepoz.md │ ├── cu.md │ ├── koda.md │ ├── danyee.md │ ├── nabom.md │ ├── neozal.md │ ├── pomo.md │ └── joanne.md ├── 3절-항상-인터페이스를-사용하세요 │ ├── empty.txt │ ├── nabom.md │ ├── cu.md │ ├── wedge.md │ ├── neozal.md │ ├── ryan.md │ ├── koda.md │ ├── wannte.md │ ├── bepoz.md │ ├── pomo.md │ ├── suri.md │ ├── danyee.md │ ├── joanne.md │ └── gump.md ├── 4절-메서드-이름을-신중하게-선택하세요 │ ├── empty.md │ ├── suri.md │ ├── danyee.md │ ├── joanne.md │ ├── nabom.md │ ├── cu.md │ ├── bepoz.md │ ├── wedge.md │ ├── pomo.md │ ├── koda.md │ ├── ryan.md │ ├── neozal.md │ └── gump.md ├── 5절-퍼블릭-상수를-사용하지-마세요 │ ├── empty.md │ ├── ryan.md │ ├── wedge.md │ ├── neozal.md │ ├── cu.md │ ├── suri.md │ ├── joanne.md │ ├── bepoz.md │ ├── pomo.md │ ├── koda.md │ ├── nabom.md │ ├── danyee.md │ └── gump.md ├── 7절-문서를-작성하는-대신-테스트를-만드세요 │ ├── empty.md │ ├── pomo.md │ ├── nabom.md │ ├── suri.md │ ├── neozal.md │ ├── cu.md │ ├── ryan.md │ ├── joanne.md │ ├── koda.md │ ├── bepoz.md │ ├── wedge.md │ ├── danyee.md │ └── gump.md ├── 8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요 │ ├── empty.md │ ├── suri_2.8.md │ ├── bepoz.md │ ├── ryan.md │ ├── wedge.md │ ├── joy.md │ ├── joanne.md │ ├── pomo.md │ ├── nabom.md │ ├── koda.md │ ├── danyee.md │ ├── cu.md │ └── gump.md ├── 9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요 │ ├── empty.md │ ├── wedge.md │ ├── suri_2.9.md │ ├── joy.md │ ├── joanne.md │ ├── nabom.md │ ├── danyee.md │ ├── bepoz.md │ ├── ryan.md │ ├── koda.md │ ├── cu.md │ ├── pomo.md │ └── gump.md ├── .DS_Store └── 1절-가능하면_적게_캡슐화하세요 │ ├── cu.md │ ├── bepoz.md │ ├── ryan.md │ ├── neozal.md │ ├── wannte.md │ ├── joanne.md │ ├── wedge.md │ ├── koda.md │ ├── pomo.md │ ├── gump.md │ ├── suri.md │ └── danyee.md ├── .gitignore ├── 3장-취업 ├── 1절-5개-이하의-public-메서드만-노 출하세요 │ ├── test.md │ ├── empty.md │ ├── cu.md │ ├── nabom.md │ ├── wedge.md │ ├── neozal.md │ ├── danyee.md │ ├── suri.md │ ├── bepoz.md │ ├── joy.md │ ├── pomo.md │ ├── joanne.md │ ├── koda.md │ └── gump.md ├── 2절-정적-메서드를-사용하지-마세요 │ ├── empty.md │ ├── bepoz.md │ ├── nabom.md │ ├── suri.md │ ├── joy.md │ ├── wedge.md │ ├── danyee.md │ ├── joanne.md │ ├── koda.md │ └── pomo.md ├── 7절-인트로스펙션과-캐스팅을-피하세요 │ ├── empty.md │ ├── wedge.md │ ├── suri.md │ ├── nabom.md │ ├── bepoz.md │ └── joanne.md ├── 3절-인자의-값으로-NULL을-절대-허용하지-마세요 │ ├── empty.md │ ├── wedge.md │ ├── koda.md │ ├── joy.md │ ├── danyee.md │ ├── bepoz.md │ ├── suri.md │ ├── joanne.md │ └── gump.md ├── 4절-충성스러우면서-불변이거나,-아니면-상수이거나 │ ├── empty.md │ ├── wedge.md │ ├── joanne.md │ ├── joy.md │ ├── suri.md │ ├── bepoz.md │ └── gump.md ├── 6절-부-ctor-밖에서는-new를-사용하지-마세요 │ ├── empty.md │ ├── pomo.md │ ├── bepoz.md │ ├── joanne.md │ ├── nabom.md │ ├── wedge.md │ ├── joy.md │ └── suri.md └── 5절-절대-getter와-setter를-사용하지-마세요 │ ├── empty.md │ ├── wedge.md │ ├── joanne.md │ ├── suri.md │ ├── joy.md │ ├── nabom.md │ ├── pomo.md │ └── bepoz.md ├── 4장-은퇴 ├── 2절-체크_예외만_던지세요 │ ├── empty.md │ ├── bepoz.md │ └── wedge.md ├── 4절-RAII를_사용하세요 │ ├── empty.md │ └── wedge.md ├── 1절-절대_NULL을_반환하지_마세요 │ ├── empty.md │ ├── wedge.md │ ├── joanne.md │ ├── bepoz.md │ └── nabom.md └── 3절-final이나_abstract이거나 │ ├── empty.md │ ├── wedge.md │ └── bepoz.md ├── 1장-출생 ├── 3절-생성자에_코드를_넣지_마세요 │ ├── nabom.md │ ├── bepoz.md │ ├── ryan.md │ ├── cu.md │ ├── koda.md │ ├── joanne.md │ ├── suri.md │ ├── pomo.md │ ├── wedge.md │ ├── wannte.md │ ├── danyee.md │ ├── neozal.md │ └── gump.md ├── 2절-생성자_하나를_주_생성자로_만드세요 │ ├── nabom.md │ ├── ryan.md │ ├── wedge.md │ ├── bepoz.md │ ├── gump.md │ ├── joy.md │ ├── suri.md │ ├── cu.md │ ├── wannte.md │ ├── koda.md │ ├── danyee.md │ ├── joanne.md │ ├── pomo.md │ └── neozal.md └── 1절-er로_끝나는_이름을_사용하지_마세요 │ ├── pomo.md │ ├── suri.md │ ├── wedge.md │ ├── wannte.md │ ├── joy.md │ ├── nabom.md │ ├── joanne.md │ ├── ryan.md │ ├── bepoz.md │ ├── danyee.md │ ├── cu.md │ ├── gump.md │ ├── koda.md │ └── neozal.md ├── .DS_Store └── README.md /2장-학습/2절-최소한_뭔가는_캡슐화하세요/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/empty.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/empty.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/empty.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/empty.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/test.md: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /4장-은퇴/2절-체크_예외만_던지세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /4장-은퇴/4절-RAII를_사용하세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/nabom.md: -------------------------------------------------------------------------------- 1 | blablabla~~~ 2 | -------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /3장-취업/7절-인트로스펙션과-캐스팅을-피하세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /4장-은퇴/1절-절대_NULL을_반환하지_마세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /4장-은퇴/3절-final이나_abstract이거나/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /3장-취업/3절-인자의-값으로-NULL을-절대-허용하지-마세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /3장-취업/4절-충성스러우면서-불변이거나,-아니면-상수이거나/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /3장-취업/6절-부-ctor-밖에서는-new를-사용하지-마세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /3장-취업/5절-절대-getter와-setter를-사용하지-마세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/empty.md: -------------------------------------------------------------------------------- 1 | empty 2 | 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-moltudy/elegant-object-study/HEAD/.DS_Store -------------------------------------------------------------------------------- /2장-학습/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-moltudy/elegant-object-study/HEAD/2장-학습/.DS_Store -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/nabom.md: -------------------------------------------------------------------------------- 1 | - 클래스 서로간 소통할 때 인터페이스를 이용해서 소통하자. 2 | - 느슨한 결합도 덕분에 확장이 쉬워진다. 3 | 4 | 객체지향의 꽃! 다형성! 완전 동의 -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/cu.md: -------------------------------------------------------------------------------- 1 | # 2.1 가능하면 적게 캡슐화하세요 2 | 3 | - 4개 이하의 객체를 캡슐화할 것을 권장 4 | - 상태없는 객체는 존재해서는 안되고, 상태는 객체의 식별자여야 한다. 5 | - 항상 eqauls() 메서드를 오버라이드하라. 6 | -------------------------------------------------------------------------------- /4장-은퇴/3절-final이나_abstract이거나/wedge.md: -------------------------------------------------------------------------------- 1 | # final이나 abstract이거나 2 | 3 | ## 느낀점 4 | 이번 내용에 대해선 공감했다. 상속은 확장이 아니라 정제를 위해 사용해야한다! 좋은 말이다. 5 | 기능 확장은 composition을 통해 표현하자. 6 | -------------------------------------------------------------------------------- /4장-은퇴/4절-RAII를_사용하세요/wedge.md: -------------------------------------------------------------------------------- 1 | # RAII를 사용하세요 2 | 별 내용이 없당. try-with-resources를 통해 connection을 잘 관리해주자. 아니면 내 손으로 직접 free하던지 3 | > 내 손으로 free해야 손맛이 좋다고! - 개발자 MBTI 만화 中 4 | -------------------------------------------------------------------------------- /3장-취업/3절-인자의-값으로-NULL을-절대-허용하지-마세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 인자의 값으로 NULL을 절대 허용하지 마세요 2 | 3 | # 느낀 점 4 | null은 피곤하당.. null을 주는 API는 NPE를 유발한다. 스트레스 받아~ 5 | null객체나 Optional을 통해 null에게 쓴 맛을 보여주겠다. 6 | -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/cu.md: -------------------------------------------------------------------------------- 1 | # 2.2 최소한 뭔가는 캡슐화하세요 2 | - 캡슐화된 상태는 고유한 식별자 역할을 한다. 3 | - 식별할 필요가 없이 단순히 기능을 수행할 때 (가령 Integer.parseInt()의 경우)는 상태가 불필요하다고 생각한다. (필자도 Integer.parseInt()를 사용하면서..) 4 | -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 2.7 문서를 작성하는 대신 테스트를 만든다. 2 | 3 | 우리가 코드를 깔끔하게 작성하는 것은 테스트 코드에서도 마찬가지이다.
4 | 테스트 코드는 클래스의 일부이지 독립적인 개체가 아니다.
5 | 메인 코드만큼 단위 테스트에도 관심을 기울여야한다.
-------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/nabom.md: -------------------------------------------------------------------------------- 1 | 개발자는 코드로 대화하자.. 2 | 3 | "단위 테스트는 클래스의 일부이지 독립적인 개체가 아니다." 4 | 5 | 훌륭한 단위 테스트가 작성이 된다면 사용방법까지 보여줄 수 있기 때문에 문서보다 낫다! 6 | 7 | 하지만 나의 코드를 라이브러리와 같이 api로 제공한다면..? 그때는 문서를 작성해야겠지..?! -------------------------------------------------------------------------------- /3장-취업/6절-부-ctor-밖에서는-new를-사용하지-마세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 3.6 부 생성자 밖에서는 new를 사용하지 마세요. 2 | 3 | 객체가 필요한 의존성을 직접 생성하는 대신, 우리가 생성자를 통해 의존성을 주입한다.
4 | 부 생성자를 제외한 어떤 곳에서도 new를 사용할 수 없도록 금지시킨다면, 객체들은 상호간에 충분히 분리되고 테스트 용이성과 유지보수성을 크게 향상시킬 수 있다.
-------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 최소한 무언가는 캡슐화 하기 2 | - 2-1장에서 4개 이상의 필드는 객체 분리의 신호일 수 있다는 내용을 이야기 했다. 3 | 그렇다면 필드가 적으면 적을수록 좋을까? 4 | 5 | - 그렇다고 아무것도 캡슐화하지 않는다는 것도 바람직하지 않다. 상태란 객체를 특정하는 식별자가 되므로 아무것도 캡슐화하지 않는다는 것은 아무것도 나타내지 않는다는 의미가 된다. 6 | 7 | -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/cu.md: -------------------------------------------------------------------------------- 1 | # 3.1 5개 이하의 public 메서드만 노출하세요 2 | - 작은 객체를 만들자! 작은 객체는 유지보수하기도 좋다! 클래스가 작으면 메서드와 프로퍼티가 더 가까이 있다! 응집도가 높다! 배고프면 밥을 먹자! 3 | - 하지만 실제로는 너무나도 험난한 길.. 우리는 디미터 원칙도 적용하다보니, 객체간 조합이 되어 있는 경우 public 메서드가 많아지는 경우도 있다. 4 | -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 최소한 뭔가는 캡슐화 하세요 2 | 3 | 4 | 5 | 상태와 식별자를 가지지 않고 행동만을 포함하는 프로퍼티 없는 클래스는 쓰지말라고 한다. 6 | 7 | 8 | 9 | 로또 미션에서 느꼈듯이.. 정적 메서드는 잘만 쓰면 성능향상이 될 수도 있는 부분이다.(캐싱) 10 | 11 | 자신만의 기준? 자신이 속한 집단의 기준에 따라서 유드리있게 잘 사용하면 될듯싶다. -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/suri.md: -------------------------------------------------------------------------------- 1 | # 문서를 작성하는 대신 테스트를 만드세요 2 | 3 | - 메인 코드만큼 단위테스트에도 관심을 기울여라 4 | 5 | 6 | 7 | # 느낀점 8 | 9 | 요즘 들어 테스트 코드를 작성하지 않은 경우가 많았다. 10 | 11 | 메인 코드에 집중하느라 였기 때문이다. 12 | 13 | 메인 코드만큼 단위 테스트에도 관심을 기울여라 라는 말을 명심해야 겠다. -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 2.1 가능하면 적게 캡슐화하세요 2 | 3 | ### 내용 4 | 5 | * == 연산자 대신 equals 를 사용하자 6 | * 객체 내의 요소는 최대 4까지만 가져라 7 | 8 |
9 | 10 | ### 생각 및 의문점 11 | 12 | 이번 장은 아주 동의하고 더 할 말이 없다. 필드를 많이 갖고 있으면 응집도가 떨어질 것이라고 생각한다. 그리고 객체 지향적이지 않은 느낌. -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 2.7 문서를 작성하는 대 테스트를 만드세요 2 | 3 | - 문서화는 유지보수에 중요한 요소이다. 4 | - 좋은 프로그래머는 단순한 코드를 작성한다. 5 | - 이상적인 코드는 스스로를 설명한다. 6 | - 코드를 문서화 하는 대신 깔끔한 코드를 작성하라. 7 | - 단위테스트는 클래스의 일부다. 8 | 9 | ## 내 생각 10 | 이 주제에 불만이 있는 당신, 반성하세요. -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/nabom.md: -------------------------------------------------------------------------------- 1 | 클래스가 더 작을 수록 우아함, 유지보수성, 응집도, 테스트 용이성이 향상된다. 5개라는 숫자는 특별한 이유는 없다. 공개 메서드가 5 개 이상이 된다면 고민해보자. 2 | 3 | ### 느낀점 4 | 5 | 음.... 음...? So simple... 2.1장에서 '가능하면 적게 캡슐화하세요.'와 비슷한 논리 같다. 적게 캡슐화하는데 공개 메서드가 많아질 때가 있을까? 이럴때는 어떻게 클래스를 나눌까?? -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/cu.md: -------------------------------------------------------------------------------- 1 | # 2.3 항상 인터페이스를 사용하세요. 2 | - 객체들이 서로를 필요로하기 때문에 결합된다. 결합이 강하면 유지보수성에 영향을 끼친다. 상호작용하는 다른 객체를 수정하지 않고도 유지보수를 위해 객체를 수정할 수 있도록 객체를 분리해야 한다. 3 | - 인터페이스로 구현하면 구현체를 다른 것으로 교체하더라도 영향을 최소화할 수 있다. (느슨한 결합) 4 | - 모든 Public Method가 인터페이스를 구현하도록 만들어야 한다. 5 | -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/cu.md: -------------------------------------------------------------------------------- 1 | # 2.7 문서를 작성하는 대신 테스트를 만드세요 2 | 3 | 딱히 논할 내용이 없네요. 4 | 테스트 주도 개발로 배우는 객체지향 설계와 실천은 한번 읽어보려고 사뒀는데, 올해 읽었으면 좋겠네요. 5 | 6 | - 단위테스트는 클래스의 일부이지 독립적인 개체(entity)가 아니다. 7 | - 유지보수가 가능한 클래스는 문서화가 필요하지 않다. 8 | - 개인적으론 테스트 코드의 메서드는 한글로 짓는걸 선호한다. 9 | -------------------------------------------------------------------------------- /3장-취업/6절-부-ctor-밖에서는-new를-사용하지-마세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 3.6 부 ctor 밖에서는 new를 사용하지 마세요 2 | 3 | ## 내용 4 | 5 | * 부 ctor 외의 곳에서는 new를 사용하면 안된다. 6 | * 의존성을 주입받는 식으로 진행해야한다. 7 | 8 |
9 | 10 | ## 생각 및 의문점 11 | 12 | 의존성 주입, 제어 역전과 관련이 있다고 말하고 있고, 반박할 거리가 없다. 주입해주는 방식을 따르는게 맞다고 생각한다. -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 퍼블릭 상수(Public Constant)를 사용하지 마세요 2 | 3 | public static final 상수는 사용해서는 안된다! 4 | 5 | Constants 라는 클래스에 상수를 모아놓으면 : 결합도 상승, 응집도 하락 6 | 7 | 8 | 9 | 사소해보이는 모든 상수를 클래스로 대체하라. 10 | 11 | enum도 마찬가지다! 12 | 13 | 14 | 15 | 흐음.... 16 | 17 | -------------------------------------------------------------------------------- /3장-취업/7절-인트로스펙션과-캐스팅을-피하세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 인트로스펙션과 캐스팅을 피하세요 2 | 3 | #느낀점 4 | 객체에 대한 차별금지 이야기는 조금 당황스러웠다.객체에 인격권을 부여하는 경지에 이른 저자에 대한 존경심도 일었다. 나도 저렇게 나만의 컨셉에 충실하고 싶다. 5 | 6 | instanceOf 와 캐스팅이 불편하다는 이야기에 대해선 동의한다. 인자의 타입에 따라 달라지는 메소드의 동작은 언젠가 오동작을 야기할 가능성을 심어놓는 행위라고 생각한다. 오버로딩이 가능하다면 오버로딩으로! 7 | -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/nabom.md: -------------------------------------------------------------------------------- 1 | - 너무 많은 것을 캡슐화하는 것도 좋지 않지만 아무것도 캡슐화를 하지 않는다는 것은 이 객체를 식별할 수 있는 것이 아무것도 없다는 것이다. 이 말은 즉, 객체를 자립적인 객체로 존중하지 않는다는 의미이다. 2 | - 이전에도 이야기했듯이 객체는 '행동'을 기반으로 태어나는 것이 아니라 '무엇인지'를 기반으로 태어나는 것이 객체지향이다 라는 예고르 형님의 생각이 들어있는 장이다. 3 | 4 | 객체지향 코드에서 상태가 없는 객체는 잘못된 객체이다 라는 생각에 완전히 동의한다! -------------------------------------------------------------------------------- /3장-취업/6절-부-ctor-밖에서는-new를-사용하지-마세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 3.6 부 ctor 밖에서는 new를 사용하지 마세요. 2 | 3 | 토비의 스프링에서도 비슷한 내용을 봤다. 4 | 생성되는 부분을 특정 객체 내부에서 하게 된다면 의존성을 갖게 된다. 5 | 여기서도 부 ctor 밖에서는 New를 사용하지 말라는 뜻이, 특정 객체를 생성할 때 필요한 것을 의존성 주입을 통해 해결해야하며 내부 메서드에서 연관 관계를 갖고 있지 마라는 뜻인 듯? 6 | 이렇게 되면 관심사의 변화가 생겼을 때 확장하기 용이할 것이다. 7 | 8 | -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 퍼블릭 상수를 사용하지 마라 2 | - 퍼블릭 상수는 다른 클래스에 대한 의존을 만든다 (결합도 증가) 3 | - 구현해야 하는 기능에 대해 다른 클래스의 퍼블릭 상수를 참조해야 한다(응집도 감소) 4 | - 대신 중복을 제거할 수 있다ㅎ 5 | 6 | - 저자가 중복을 제거하는 대안으로 제시한 클래스의 추가는 말그대로 객체지향적이며 우아하다고 생각한다. 7 | - 그러나 퍼블릭 상수 하나를 대처하는 하나의 클래스, 수백 개의 클래스가 정말 유지보수하기 좋을지는 경험해봐야 알 것 같다! 8 | -------------------------------------------------------------------------------- /3장-취업/5절-절대-getter와-setter를-사용하지-마세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 절대 getter, setter를 쓰지마라 2 | - 자료구조는 글래스박스, 객체는 블랙박스라는 표현이 맘에 쏙 든다. 비유로 자주 쓸 듯 3 | 4 | # 느낀점 5 | - 우테코에서 그동안 익힌 바에 의하면 클래스는 데이터 집합체 그 이상이다. getter, setter의 남용은 객체를 데이터 집합체로 만들고, 해당 데이터를 가져와 로직을 수행하는 '프로세서'의 출현을 예고한다. 6 | 객체 지향과는 조금씩 멀어진다는 점 => 유지보수 비용이 증가한다는 점 7 | -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 문서를 작성하는 대신 테스트를 만드세요 2 | 3 | 이상적인 코드는 주석 등 추가문서 필요없음. 4 | 5 | 코드만 읽어도 잘읽혀야함 6 | 7 | 8 | 9 | 깔끔하게 만든다는 말에는 10 | 11 | 단위테스트도 함께 만든다는 의미 포함 12 | 13 | 14 | 15 | 단위테스트는 클래스의 일부이지 독립된 개체가 아님. 16 | 17 | 단위테스트 == 문서화 18 | 19 | 20 | 21 | 22 | 23 | 6절과 7절은 인정. 24 | 25 | -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/suri_2.8.md: -------------------------------------------------------------------------------- 1 | # 2.8 모의 객체 대신 페이크 객체를 사용하세요. 2 | 3 | 4 | 5 | - 모킹을 사용하지 말고 항상 인터페이스 대해 페이크 클래스를 만들어라 6 | 7 | 8 | 9 | # 느낀점 10 | 11 | 모킹을 사용해 보지 않아서 많이 와닫지 않는 챕터 였다. 12 | 13 | 테스트를 위해 인터페이스에 대해 굳이 페이크 객체를 만들어야 할 필요가 있을까? 14 | 15 | 전략패턴 처럼 구현체를 테스트에 알맞도록 구현하여 넘겨주는 방식이 더 효과적이지 않을까? -------------------------------------------------------------------------------- /3장-취업/6절-부-ctor-밖에서는-new를-사용하지-마세요/nabom.md: -------------------------------------------------------------------------------- 1 | 의존성을 주입하지 않고 하드 코딩으로 의존을 하는 코드는 나쁜 코드이다. 2 | 유연한 코드가 아니기 때문에 유지보수 그리고 테스트가 힘들어진다. 3 | new를 사용해 인스턴스를 생성해서 의존을 하는 방법은 부 생성자에서만 하는 것이 좋다. 4 | (default 값으로 설정) 5 | 6 | ### 느낀점 7 | 확실히 인스턴스는 외부에서 받아 사용하게되면 확장성이 좋아진다. 8 | 그러다보니 습관적으로 외부에서 구현 객체를 받아 사용하게 된다. 9 | 문뜩, 궁금한 것이 있다. 과연 진짜 강한 결합은 절대 일어나서는 안되는 것일까? -------------------------------------------------------------------------------- /3장-취업/6절-부-ctor-밖에서는-new를-사용하지-마세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 부 생성자 밖에서는 new를 쓰지 마세요 2 | 3 | # 느낀점 4 | - 선생님.. 의존성 주입이 싫은 이유는 언제 말씀하시는 거죠? 의존성 주입 캡짱 최고 이것이 객체지향이다 라는 이야기 밖에 안보이는데요 5 | - 클래스가 작고 단순하며, 외부자원을 사용하지 않는다면 new를 사용해도 괜찮을 것 같다. 저자가 말한 예시와 같은 경우 (클래스를 사용할 때마다 NYSE 거래소에 요청을 날리는)에는 해당 내용을 추상화 하는 것이 충분히 합리적이라는 생각이 든다. 외부 접속 의존성에 대해선 변경 가능성에 대해 항상 고려해야 한다. 6 | -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 가능하면 적게 캡슐화하세요 2 | 3 | - 상태없는 객체는 존재해서는 안되고, 상태는 객체의 식별자여야 한다. 4 | 5 | 6 | 7 | - 캡슐화 하기에 적합한 객체의 수 : 4개 8 | - 4개까지가 직관에 좋다 9 | - 더 많은 좌표와 속성이 필요하면 더 그룹화 해라 10 | 11 | 12 | 13 | 14 | 15 | 4개는 본인만의 기준 아닐까, 16 | 17 | 일곱개의 법칙이라는게 있는데 보통사람의 기억은 한번에 일곱단위까지 기억한다고한다. 18 | 19 | 나는 7개까지 허용할랜다. 20 | 21 | -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 불변 객체로 만드세요 2 | 3 | - 모든 클래스를 불변으로 만들면 유지보수성 향상 4 | 5 | 6 | 7 | #### 불변객체란? 8 | 9 | - 인스턴트를 생성한 후에 상태를 변경할 수 없는 객체 10 | - 수정해야 한다면 수정 대신 새로운 객체 생성 11 | 12 | 13 | 14 | 장점 15 | 16 | - 식별자 가변성: 불변객체 사용시 식별자가 확실히 구분됨 17 | - 실패 원자성: 완벽할때 새로운 객체를 인스턴스화함 18 | - 시간적 결합 19 | - 부수효과 제거 20 | - NULL 참조 제거 21 | - 스레드 안정성 -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 2.2 최소한 뭔가는 캡슐화하세요 2 | 3 | - 어떤 것도 캡슐화 하지 않은 객체는 모두 동일하다. 4 | - 자신이 '무'가 아니라면 무엇인가는 캡슐화 해야 한다. 5 | - 자신을 식별할 수 있어야 다른 객체들과의 협업이 가능하다. 6 | 7 | # 내 생각 8 | 완전히 공감한다. 당연히 객체는 자신의 "무엇인지" 나타내기 위해서 자신의 식별자를 가져야 한다. 9 | 10 | static에 관련해서 많은 의견이 오간다. 나 또한 static을 사용하지 말자는 주의에 가깝다. 11 | 하지만 개발의 편의성을 위해서 조금의 일탈은 가능하지 않을까? -------------------------------------------------------------------------------- /3장-취업/5절-절대-getter와-setter를-사용하지-마세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 3.5 절대 getter와 setter를 사용하지마세요. 2 | 3 | 이 장은 제목을 좀 헷갈리게 지은 것 같다. 4 | 내 생각에 저자의 의견은 메서드명에 get / set을 쓰지 말라! 인 듯. 5 | 왜냐면? 프로퍼티로 String name;을 갖는 경우 getName()은 안되고, name()은 된다고 하기 때문이다. 6 | 7 | 사실 이 장을 전에도 봤던 것 같은데, getName()이 아니라 name()을 쓰니 뭔가 게터를 안쓰는 것 같다는 마음의 위안을 얻긴 했는데, 이렇게 메서드명만 바꾼다고 getter를 사용하지 않는 것이 되는걸까? -------------------------------------------------------------------------------- /3장-취업/5절-절대-getter와-setter를-사용하지-마세요/suri.md: -------------------------------------------------------------------------------- 1 | # 절대 getter와 setter를 사용하지 마세요. 2 | 3 | - 데이터 가방과 객체를 구분하자 4 | - 자료 구조는 투명하지만, 객체는 불투명합니다. 5 | - 모든 것은 유지보수정과 관련이 있다. 6 | - 프로그래밍 스타일의 핵심 목표는 가시성의 범위를 축소해서 사물을 단순화 시키는 것 7 | - 객채지향적이고 선언형 스타일을 유지하기 위해서는 데이터를 객체 안에 감추고 절대로 외부에 노출해서는 안됨 8 | 9 | 10 | 11 | # 느낀점 12 | 13 | - 아직 dollars()와 getDollars()차이가 와닿지 않는다.... -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 항상 인터페이스를 사용하세요 2 | - 객체가 의사소통하기 위해선 결합(coupling)이 필요하다. 3 | - 하지만 결합도가 지나치게 높다면 유지보수성이 악화된다. (한 클래스의 코드변화에 여러 클래스가 수정되어야 한다.) 4 | - 원할한 의사소통을 하면서 결합도는 낮추려면, 객체간의 계약을 명시한 인터페이스를 활용할 수 있다. 5 | 6 | ### 느낀 점 7 | - 하지만 뭐든지 정도가 중요하다. 모든 객체가 인터페이스를 활용한다면, 그리고 모든 프로퍼티를 인터페이스로 가지고 있다면 되려 다른 개발자가 어떤 구현체가 주입되어있는지 몰라 헤매는 불상사가 생길 것 같다. (개인적인 경험) 8 | -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 5개 이하의 public 메소드만 노출하세요 2 | 3 | # 느낀점 4 | - 클래스가 작을 때 장점들은 미션에서 쉽게 접해볼 수 있었다. 테스트 짜기 좋고, 디버깅이 쉽고, 개념이 단순해지고, 기능을 확장하기도 용이하다. 5 | 6 | > 클래스에 두개의 프로퍼티가 있을 때 한 프로퍼티는 두 개의 메서드에서만 사용되고 나머지 프로퍼티는 다른 세 개의 메서드에서만 사용된다면, 거의 연관성이 없는 독립적인 두 부분이 하나의 클래스 안에 뭉쳐있는 상황이라고 할 수 있습니다. 7 | 8 | 라는 내용이 인상깊었다. 응집성을 설명할 수 있는 좋은 키워드를 하나 더 얻어간 느낌! 9 | -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/wannte.md: -------------------------------------------------------------------------------- 1 | # 최소한 뭔가는 캡슐화하세요 2 | 3 | ## 내용 정리 4 | 아무것도 캡슐화하지 않는 객체는 바람직하지 않다.(정적 메서드와 유사) 5 | 캡슐화된 상태가 세계 안에서 객체의 위치를 지정하는 고유 식별자인데, 어떤 것도 캡슐화하지 않는다면, 객체의 좌표는 그 자신이 세계 전체가 되어야 한다. 6 | 7 | ## 느낀 점 8 | property가 없는 객체를 써야만 하는 상황을 접한 적이 있는가? Controller를 여기에 포함시켜야 하나. 9 | property가 없는 객체를 있게끔 바꾼 경험이 따로 떠올르지 않아, 공감가긴 힘들었다. 10 | 나중에 경험해볼 수 있지 않을까.? 11 | -------------------------------------------------------------------------------- /3장-취업/7절-인트로스펙션과-캐스팅을-피하세요/suri.md: -------------------------------------------------------------------------------- 1 | # 3.7 인스트로스펙션과 캐스팅을 피하세요 2 | 3 | - instanceof 연산자 Class.cast 메서드 4 | - 프로그래머는 런타임에 객체의 타입을 확인 할 수 있다. 5 | - 리플렉션 6 | - 코드가 런타임에 다른 코드에 의해 수정된다는 사실은 매우 끔찍 7 | 8 | 9 | 10 | - 우리는 차별하지 말고 객체가 누구건 상관없이 자신의 일을 할 수 있도록 해야 합니다. 11 | 12 | 13 | 14 | # 느낀점 15 | 16 | - 체스 구현 시 피스 인터페이스가 isKing(), isPawn()을 가지고 자신을 판별하게 하게 하게 했는데 잘한듯..? -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/nabom.md: -------------------------------------------------------------------------------- 1 | ## 정리 2 | 3 | 2절은 굉장히 간단하고도 중요하다. 4 | 5 | 주 생성자는 단 하나여야 한다. 다른 생성자들은 주 생성자가 아닌 부 생성자이고 값을 완성해 주 생성자에게 가져다주는 것이다. 이렇게 하는 이유는 중복 코드를 방지할 수 있고 유지보수성이 향상되기 때문이다. 6 | 7 | ## 느낀점 8 | 9 | 확실히 생성자 하나를 주 생성자로 만들어 이 주 생성자를 통해 부 생성자를 선언한다면 유지보수성이 엄청 향상된다. 예를 들어, 유효성 검사를 할 때에도 주 생성자에만 추가해준다면 모든 부 생성자 또한 유효성 검사를 받을 수 있게 된다. 완전 동의! 200프로 동의! 10 | -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 2.5 퍼블릭 상수를 사용하지 마세요. 2 | 3 | - 상수는 데이터를 공유하기 위함 4 | - 높은 결합도를 만들어 낸다. 5 | - 상수를 사용하는 대신 재사용 가능한 클래스를 생성하라. 6 | 7 | # 내 생각 8 | 9 | 좋은 것 같기도 하고 나쁜 것 같기도 하고... 10 | public 상수로 결합도가 높아지는건 충분히 공감한다. 11 | 여러곳에서 반복되는 상수의 경우도 클래스로 만들어 사용하는것, 좋다고 생각한다. 12 | 객체의 협력을 만들어서 상수를 처리하는 매커니즘이 아름답다고 느꼈따. 13 | 그래서 내가 이 방법을 사용할꺼냐 물으면.. 많은 고민이 필요할 것 같다... -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 항상 인터페이스를 사용하세요 2 | - 객체들의 강한 결합을 분리하기 위해 인터페이스를 사용해야 한다. 3 | 4 | # 내 생각 5 | 많이 공감하는 내용이다. 물론 "이론적으로만!" 6 | 7 | 실제에서는 서로 의존성을 띄는 객체가 변경이 없는경우일수도 있고, 여러 변수가 존재한다. 이런 경우는 interface를 사용하는건 오히려 8 | 오버 테크놀로지라고 생각하다. 9 | 10 | 그리고 개인적이로 이 장도 마음에 안든다. interface를 사용하면 결합도를 낮출 수 있다는 점은 잘 알고, DIP또한 만족하는것도 안다. 11 | 그런데 그에 대한 설명없이 그냥 하라고 말하는게 약간 이런말하면 뭐하지만 좋좋소기업 같다. 12 | -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 2.7 문서를 작성하는 대신 테스트를 만드세요. 2 | 3 | 문서화가 필요하다는 것은, 코드가 깔끔하지 않다는 증거이다. 4 | 우선 스스로를 설명할 수 있는 이상적인 코드를 짜는 것이 중요하다. 그렇지 않다면 문서화를 통해 설명을 포함해야한다. 5 | 여기서 이상적인 코드에는 단위 테스트도 포함되어있다. 단위 테스트는 코드로서 클래스의 사용 방법을 보여준다. 깔끔하고 유지보수 가능한 단위 테스트를 추가하면, 클래스를 더 깔끔하게 만들 수 있고 유지보수성을 향상시킬 수 있다. 6 | 7 | 단위 테스트 자체만으로도 문서화가 되고, 작동 방식을 통한 클래스의 사용 방식을 보여줄 수 있다. main 코드를 짜는 것처럼 단위 테스트에도 관심을 기울이자. -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 생성자 하나를 주 생성자로 만드세요 2 | 3 | - 생성자의 갯수가 메서드의 갯수보다 많아야한다. 4 | 5 | - 2-3개의 메서드와 5-10개의 생성자 포함하는것이 적당 6 | 7 | 8 | 9 | - ctor의 갯수가 많을 수록 사용하기 편리 10 | 11 | - 메서드가 많으면 클래스의 초점이 흐려짐 12 | 13 | - 단일책임원칙 위반 14 | 15 | 16 | 17 | - 주 생성자에서 유효성 검증을 하세요. 18 | 19 | 20 | 21 | 생성자의 갯수가 많으면 사용하기 편하다는 의견에는 동의. 메서드의 갯수가 2-3개 포함하여야 한다는것은 좀.. 현실과는 동떨어지지 않았나 싶음.... 22 | 23 | -------------------------------------------------------------------------------- /3장-취업/7절-인트로스펙션과-캐스팅을-피하세요/nabom.md: -------------------------------------------------------------------------------- 1 | 먼저, 인트로스펙션은 리플렉션이라는 더 포괄적인 용어로 불리는 여러 가지 기법들 중 하나이다. 리플렉션을 사용하면 모든 요소에 접근이 간단하지만 동시에 코드를 유지보수하기 어렵게 만드는 매우 너저분한 기법이다. 2 | 3 | `instance of` 를 사용해 객체를 차별하며 진행하는 방식도 OOP의 기본 사상에 어긋난다. 차별하는 대신 객체가 결정할 수 있도록 설계를 해야한다. 4 | 5 | ### 느낀점 6 | 동의하는 부분이다. 하지만 리플렉션이라는 기법을 사용하지 않는다면 어노테이션을 사용하는 방법이 불가능하다. 7 | 물론 리플렉션을 마구마구 사용하면 나쁘지만 나의 코드를 라이브러리로 편의성을 제공하고 싶다면 어노테이션 기반 리플렉션을 사용해도 괜찮지 않을까? -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/cu.md: -------------------------------------------------------------------------------- 1 | # 2.5 퍼블릭 상수를 사용하지 마세요 2 | 3 | - 퍼블릭 상수마다 계약의 의미를 캡슐화하는 새로운 클래스를 만들어야 한다고?! 그리고 method chaining에 리터럴을 사용하는 것 보다 클래스 중첩이 더 좋다고? 4 | 5 | ```java 6 | new HttpRequest().method(POST); 7 | new PostRequest(new HttpRequest()); 8 | ``` 9 | 10 | - 값을 하드코딩하기보다는 도메인 내에서 사용하도록 private 상수를 두면 되지 않은가? 11 | - URL의 경우 Controller에서의 중복을 제거하는 것 외에도 테스트코드에서도 재사용할 수 있어 실용적인데 URL도 별도의 클래스로 작성하라는 말인가? 12 | -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 3.1 5개 이하의 public 메서드만 노출하세요. 2 | 3 | ### 내용 4 | 5 | - 클래스를 작게 유지해야 한다. 6 | - 클래스의 길이를 짧게 유지하는게 아니라, public method를 작게 유지하는게 중요하다. 7 | - 5개가 적절한 수준의 public메서드 이다. 8 | 9 | ### 생각 및 느낀점 10 | 11 | - 딱히 반박할 거리도 없다. 12 | - public메서드를 작게 유지한다는 것은, 객체의 책임이 작아진다는 것을 의미한다. 13 | - 따라서 이를 통해 객체의 책임을 작게 유지하고 유지보수성을 올릴 수 있을것이라 생각한다. 14 | - 다만, public메서드를 5개 이하를 유지하는것과 함께 응집도와 관련된 이야기를 함께 해 주었으면 더 좋았을 것 같다. -------------------------------------------------------------------------------- /3장-취업/6절-부-ctor-밖에서는-new를-사용하지-마세요/joy.md: -------------------------------------------------------------------------------- 1 | # 부 ctor 밖에서는 new를 사용하지 마세요 2 | ## 정리 3 | 주 생성자에서 new를 사용하면 의존성이 하드코딩되어 유연하지 못한 클래스가 된다. 4 | 하드코딩된 의존성을 피하기 위해서는 외부에서 의존성을 주입해야한다. 5 | 부 생성자에서는 new로 새로운 객체를 만들어줘도 괜찮다. 이미 주 생성자에서 의존성을 모두 분리했기 때문에 편의를 위해 부 생성자에서는 new를 써도 좋다. 6 | 메서드도 마찬가지로 new를 쓰면 유연하지 못하다. 7 | 8 | ## 느낀점 9 | 좋은 내용이다. 결합도를 낮추고 유연한 객체를 만드는 좋은 프렉티스라고 생각한다. 10 | 맨 마지막에 갑자기 IoC가 나와서 띠용? 내가 아직 IoC를 잘몰라서 그런건가. 이번장의 어느부분에서도 IoC와 관련된 내용은 없어보였다; 11 | -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 인터페이스를 짧게 유지하고 스마트를 사용하라 2 | - 인터페이스 내부에 데코레이터를 제공하라 3 | 4 | # 느낀점 5 | - Fake객체와 달리 Smart객체에 필요성에 대해선 이해하기 어렵다. defualt method와 비교했을 때 Smart객체의 장점이 무엇이 있을까? 6 | 구현체이므로 상태를 활용할 수 있는것? 7 | 8 | 그럼 abstract class와 비교했을 땐? 9 | '_' 차이가 없어보이는데... 인터페이스에서 직접 제공할 수 있다는 것이 얼마나 큰 이점을 가질 수 있을까? 10 | 11 | 이건 써봐야 알 것 같다. 다음 미션 때 적용해봐야겠다. 그런데 Fake Class와 달리 이건 설득할 근거가 없다 ㅋㅋㅋ 2.8 때 제시한 아찔하게 만들기 작전이 유효할거 같긴 하다 12 | -------------------------------------------------------------------------------- /3장-취업/3절-인자의-값으로-NULL을-절대-허용하지-마세요/koda.md: -------------------------------------------------------------------------------- 1 | # 3.3 인자의 값으로 NULL을 절대 허용하지 마세요 2 | 3 | ## 내용 4 | 5 | - 메서드 인자로 null을 허용해서는 안된다. 6 | 7 | null을 반환하는 행위는 객체를 존중하는 것이 아니라 객체를 무시하는 행위라서 객체지향적이지 않다. 8 | 9 | '아무것도 없음' 상태를 표현하기 위해서는 해당 객체에서 요청을 거부하도록 설계를 해서 구현을 해야 한다. (항상 객체를 전달해야 한다.) 10 | 11 | ## 의견 및 의문 12 | 13 | - 어디선가 null을 사용하다가 오류가 나면 해당 오류를 찾기가 힘든 것 같다. 14 | - 또한 작가가 말한 것처럼 어디선가 null을 의도적으로 쓴다면 해당 객체를 null 상태를 표현할 수 있도록 구현할 수 있을 것이라고 생각한다. 15 | 16 | -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 2.1 가능하면 적게 캡슐화 하세요. 2 | 3 | - 4개 또는 그 이하의 객체를 캡슐화 하라. 4 | 5 | # 내 생각 6 | 예고르 아저씨가 약먹었나? 하는 절이었다. 7 | 8 | 이러한 생각은 너무나도 데이터 중심적 생각이며 객체지향적 사고를 무시한다. 9 | 10 | 의미는 안다. 내부의 데이터가 적아야 응집도가 올라갈 가능성이 높아진다는 것. 하지만 지금 설명을 읽어보면 11 | 객체의 내부 데이터가 상태든 뭐든 그냥 4개 아래로만 유지하면 ok라고 한다. 12 | 13 | 이는 잘못하면 클래스를 "추상 데이터 타입"으로 만들 가능성이 농후한 글이다. 추상데이터타입은 오퍼레이션을 기준으로 타입을 묶는다. 14 | 15 | 객체지향적이라면 "절차"를 중심으로 타입을 묵고 다형성을 만들어내야 한다. 즉 절차 추상화를 이뤄야 한다. -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/wannte.md: -------------------------------------------------------------------------------- 1 | # 2.1 가능하면 적게 캡슐화하세요 2 | ## 내용 정리 3 | | 4개 또는 그 이하의 객체를 캡슐화 해라. 4 | | 상태 없는 객체는 존재해서는 안되고, 상태는 객체의 식별자여야 한다. 5 | equals() 메소드를 오버라이드 해서 사용해라. (@EqaulsAndHashCOde) 6 | 7 | # ? 8 | 로또를 뽑는 경우 동일한 로또 번호가 나올 가능성이 존재한다. 9 | 10 | 이러한 경우에는 11 | ```java 12 | class Lotto { 13 | Long serialNumber; 14 | LottoNumbers lottoNumbers; 15 | } 16 | ``` 17 | 18 | 같은 형식으로 구분을 해줘야 하나? 아니면 equals를 구현하지 않는 방식으로 진행하는 것이 좋을까? 라는 의문이 든다. 19 | -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/suri_2.9.md: -------------------------------------------------------------------------------- 1 | # 2.9 인터페이스를 짧게 유지하고 스마트를 사용하세요 2 | 3 | - 클래스를 작게 만드는 것이 중요하다면 인터페이스를 작게 만드는 것은 훨씬 더 중요하다 4 | - 클래스가 다수의 인터페이스를 구현하기 때문에 5 | 6 | 7 | 8 | - 공통 기능을 제공하는 스마트 클래스를 만들어라 9 | 10 | 11 | 12 | # 느낀점 13 | 14 | - 블랙잭 미션에서 중복을 줄이기 위해 인터페이스의 default 메소드와 추상 골격 구현 메소드에 대해 공부 해 보았다. 15 | - 추상 골격 메소드로 중복을 많이 줄였었다. 16 | - 이번 챕터도 저번 챕터와 마찬가지로 생소하다. 17 | - 기회가 된다면... 추상골격구현메소드, default 메소드, 이번 스마트 클래스를 비교하는 포스팅을 해봐야 겠다. -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 항상 인터페이스를 사용하세요 2 | 3 | 항상? 4 | 5 | - 애플리케이션이 커지면 coupling 이 문제가 되기 시작한다. 6 | - 유지보수를 위해선 최선을 다해 decoupling 을 해야한다. 7 | - 객체분리란 상호작용하는 다른 객체를 수정하지 않고도 해당 객체를 수정하는것 -> 인터페이스가 이를 수행하기 위한 가장 좋은 도구 8 | 9 | - 느슨한 결합도: 동일한 인터페이스를 구현하는 여러 클래스들이 존재하며, 각각의 클래스는 다른 클래스를 대체할 수 있어야함. 10 | 11 | 12 | 13 | 인터페이스는 확실히 유지보수하기에 편리한 장점이 있는것 같다. 예를들어 사용하는 db를 바꾸게 될 일이 있다거나.. 체스판에서 새로운 피스가 생긴다거나 등. 레벨1미션을 하면서 제일 와닿았던거중 하나가 인터페이스였던거 같다! 14 | 15 | -------------------------------------------------------------------------------- /3장-취업/4절-충성스러우면서-불변이거나,-아니면-상수이거나/wedge.md: -------------------------------------------------------------------------------- 1 | # 충성스러우면서 불변이거나, 아니면 상수이거나 2 | 1. 상수 객체 : 불변 객체의 일부이며, 같은 메소드 호출에 대해 항상 같은 값을 반환한다. 3 | 2. 불변 객체 : 상태가 불변하며, 그러므로 식별자 없이 전체 상태를 식별자로 활용할 수 있다. 4 | 5 | # 느낀 점 6 | 불변사랑 나라사랑 7 | 8 | > 불변객체는 엔티티에 충성한다. 9 | 10 | -> 자신이 대표하고 있는 실세계의 은유를 배반하지 않는다.(기대받는대로 행동한다.) 11 | 12 | 울림이 있는 메시지라고 생각한다. 클래스를 만들 때 '아 이럴 땐 제대로 기능 안 할텐데...' 싶을 때가 있다. 먼 시간이 지난 후 해당 이유로 인해 디버깅이 필요한 시점이 오면 "아 맞다!!!" 하게 되는데..ㅎ 13 | 엔티티에 충성하는 객체를 만들겠다!! 충성충성 14 | -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 생성자 하나를 주 생성자로 만들어라. 2 | - 생성자는 풍부하면 풍부할수록 좋다. 3 | - 숫자가 많아지면 객체의 역할이 점점 모호해지는 public method와는 달리, 다양한 부 생성자는 객체를 유연하게 하고 중복코드를 방지해준다. 4 | - 그러나 프로퍼티에 집적 인자를 할당하는 주 생성자는 오직 하나 뿐이어야 한다. 5 | - 얻을 수 있는 효과 : 여러 생성자에서 해주어야 할 일을 단 `한 번`만 수행할 수 있다. 6 | 7 | - ※ 주 생성자 위치 팁 : 모든 생성자 중 가장 뒤 쪽에 위치시킨다. 수많은 부 생성자 사이에서 주 생성자를 찾아야 하는 난감함을 줄여준다. 8 | 9 | # 궁금한 점 10 | - 저자는 static이 절대 악이라고 했는데, 그렇다면 부 생성자에서 언어의 한계로 인해 사용하는 변환용 정적메서드는 허용되는 것인가? 11 | -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/joanne.md: -------------------------------------------------------------------------------- 1 | ## 2.1 가능하면 적게 캡슐화하세요. 2 | 3 | 내부 필드는 4개 또는 그 이해의 객체를 캡슐화하자. 내부에 캡슐화된 객체를 가리켜 객체의 상태 또는 식별자라고 부른다. 객체를 판별할 때 그 상태들을 비교하여 판별하기 때문에 만약 3개의 상태를 갖고 있다면 그 3가지 상태가 모두 같아야 같은 객체라고 볼 수 있다. 4가지인 이유는 직관을 통해 이해하기에는 4가지까지가 적합하기 때문이다. (좌표를 생각) 4 | 5 | 또한 더불어 == 연산자를 사용하지 말고 항상 equals() 메서드를 오버라이드 해 사용하자. 6 | 7 | ### 의견 8 | 상태가 객체의 식별자라면, 그 개수를 줄여야한다는 것에 동의한다. 상태가 많아지면 내부에서 확인해야할 것도 많아지고, 비교해야할 때도 체크해야할 것들이 많아지는 것은 사실이다. 필드의 수를 줄이기 위해 더 작은 클래스로 분리하고 조합하는 과정을 수행하자! -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/joy.md: -------------------------------------------------------------------------------- 1 | # 2.9 인터페이스를 짧게 유지하고 스마트(Smart)를 사용하세요 2 | ## 정리 3 | 4 | 인터페이스의 메서드가 많으면 그걸 구현하는 클래스는 단일 책임 원칙을 지키기 어렵고 응집도가 낮아진다. 5 | 추상메서드끼리 연관성이 있다면 하나의 메서드만 추상메서드로 남겨두고 나머지는 스마트 클래스에 구현해서 해결한다. 6 | 인터페이스를 구현하는 서로 다른 클래스에 동일한 구현을 반복하는건 좋지 않다. 중복되는 메서드 구현을 스마트안에 넣으면 해결할 수 있다. 7 | 8 | ## 느낀점 9 | - 인터페이스가 작고 응집도 높아야한다는 말에 동의한다. 경험상 클래스보다 인터페이스의 변경이 훨씬 파급력있고 치명적이었기 때문이다. 10 | - 인터페이스를 작게 유지해야하는걸 알고는 있었지만 어떻게 실천해야할지 잘몰랐는데 이번 챕터에서 구체적인 방법을 배워서 좋았다. 11 | -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 3.1 5개 이하의 public 메소드만 노출하세요 2 | 3 | ## 정리 4 | - 가장 우아하고, 유지보수가 가능하고, 응집력이 높으면서, 테스트하기도 용이한 객체 👉 작은 객체 5 | - 클래스를 작게 만들기 위해 public/protected 메소드의 수를 5개 이하로 유지해야 한다. 6 | - public/protected 메소드가 많을수록 클래스의 크기는 커진다. 유지보수성은 저하된다. 7 | - 클래스가 더 작을수록 실수할 가능성이 줄어든다. 클래스의 메소드가 더 적을수록 서로 조화를 이루기가 더 쉽다. 8 | 9 | ## 느낀점 10 | 11 | - public/protected 메소드의 수가 더 적을수록 유지보수하기 더 쉬울 것 같긴 하다. 12 | - 저자의 기준(5개 이하)를 지키기 어렵더라도 최대한 작은 객체를 만들도록 노력해봐야겠다. -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 2.8 모의 객체(Mock) 대신 페이크 객체(Fake)를 사용하세요 2 | 3 | ## 내용 4 | 5 | * 모킹 대신 페이크 객체를 사용해라 6 | * 페이크 클래스를 사용하면 테스트를 더 짧게 만들 수 있기 때문에 유지보수성이 눈에 띄게 향상된다. 반면에 모킹의 경우, 테스트가 매우 장황해지고, 이해하거나 리팩토링하기 어려워진다. 7 | * 모킹은 클래스 구현과 관련된 내부의 세부사항을 테스트와 결합시킨다. 8 | * 모킹은 나쁜 프랙티스다. 9 | 10 |
11 | 12 | ## 생각 및 의문점 13 | 14 | mock 사용경험이 이전에 MockMvc 정도고 그때에도 깊게 알지 못한채 사용을 했었다. 페이크 객체 사용의 경험은 없다. 그래서, 이번 장은 이해가 잘 가지 않았다... 헛읽은 느낌 ㅜㅜ 다른 크루들의 생각을 한 번 기다리고 있겠습니다. -------------------------------------------------------------------------------- /3장-취업/5절-절대-getter와-setter를-사용하지-마세요/joy.md: -------------------------------------------------------------------------------- 1 | # 절대 getter와 setter를 사용하지 마세요 2 | ## 정리 3 | getter, setter는 객체를 단순한 자료구조로 만든다. 4 | 자료구조의 데이터는 수동적인 존재였다. 하지만 OOP에서 데이터는 객체안에 캡슐화되었고 능동적인 존재가 되었다. 5 | 유지보수성을 높이려면, 가시성의 범위를 축소해서 사물을 단순화시켜야한다. 이해해야하는 범위가 작을수록, 유지보수성이 향상되고 수정하기 쉬워진다. 6 | get/set 접두사는 객체의 데이터를 직접 노출시키는 것과 다름없다. 7 | 8 | ## 느낀점 9 | getter/setter를 사용하지 말라는 의견에 100% 동의한다! 10 | getter/setter에 관한 챕터였지만 절차지향과 객체지향의 차이에 대해 더 많이 배운것 같다. 11 | 이번장에서 말하는 `코드`는 객체(혹은 자료구조)를 다루는 외부의 코드를 의미하는 것 같다. 12 | -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 1-2. 생성자 하나를 주 생성자로 만드세요 2 | 3 | ### 내용 4 | 5 | * 2 ~ 3 개의 메서드와 5 ~ 10 개의 생성자를 포함하는 것이 적당하다. 6 | * 메서드가 많아지면 SRP를 위반하지만, 생성자가 많아지면 유연성이 향상된다. 7 | * 주 생성자는 마지막에 정의해서 유지보수에 도움되게끔 하라. 8 | * 메서드 오버로딩을 지원하지 않는 언어들도 여러 생성자들을 생성함으로써 유연하게 만들 수 있다. 9 | 10 |
11 | 12 | ### 생각 및 의문점 13 | 14 | 이번 장은 대체적으로 동의하는 장이다. 그러나 10개에 가까운 개수의 생성자를 추가하는 것은 오히려 클라이언트에게 혼동을 줄 수 있지 않을까 라는 생각을 들게하는 것 같다. 그리고 주 생성자를 마지막에 두는 이유를 유지보수적 이유라고 했으며 곧장 찾기위해서라고 했는데 차라리 맨 위에 두는 것이 좋지 않을까 ??? -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 1-3. 생성자에 코드를 넣지 마세요 2 | 3 | ### 내용 4 | 5 | * 생성자 안에 코드를 넣지 말고 필요하다면 따로 클래스를 생성해서 처리하라 6 | * 생성자에서 코드를 없애면 사용자가 쉽게 제어할 수 있는 투명한 객체를 만들 수 있으며, 객체를 이해하고 재사용하기 쉬워지며 게으르게 동작할 수 있다 7 | 8 |
9 | 10 | ### 생각 및 의문점 11 | 12 | 다 좋다. 하지만, 객체의 수가 말도 안되게 늘어날 것이고, 값을 가지고 오는 과정이 1단계 늘어나게 돼서 처음 접하는 개발자는 피로감이 꽤나 들 것 같다. 기준을 정해서 해당 기준을 넘은 경우에만 이번 장과 같이 적용하는 것이 좋아보인다. 프로젝트가 커지면 필연적으로 중복되는 생성자 속 코드가 생길 것 같은데 (이번 장에서 예시를 든 ``Integer.parseInt`` 와 같은) 그 경우에는 더더욱 도움이 되는 방법일 것 같다. -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/suri.md: -------------------------------------------------------------------------------- 1 | # 5개 이하의 public 메서드만 노출하세요 2 | 3 | - 가장 우아하고, 유지보수가 가능하고, 응집력이 높으면서, 테스트하기도 용이한 객체는 작은 객체 4 | - 작은 객체란 private가 아닌 protected, public 메소드의 갯수로 판별이 가능 5 | - 저자는 5개 이상의 public 메소드를 가진 객체는 크다고 봄 6 | 7 | 8 | 9 | - 클래스가 작으면 메서드와 프로퍼티가 더 가까이 있을 수 있기 때문에 응집도가 높아진다. 10 | - 간단히 말해서 각각의 메서드가 클래스의 모든 프로퍼티를 사용 11 | 12 | 13 | 14 | # 느낀점 15 | 16 | - 5개라고 정해지지는 않았지만 public 메소드가 많아 졌다면 이 객체의 의미에 대해서 다시 되짚어 볼 필요성을 느꼈다. 17 | - 클래스가 작으면 메서드와 프로퍼티가 더 가까이 있을 수 있기 때문에 응집도가 높아진다. -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/koda.md: -------------------------------------------------------------------------------- 1 | # 2.7 문제를 작성하는 대신 테스트를 만드세요 2 | 3 | ## 내용 4 | 5 | 문서화가 유지보수에 있어서 중요한데 이 이유는 클래스나 메서드에 관한 추가 정보를 쉽게 얻을 수 있게하기 때문이다. 이것은 물리적인 '문서'에 관한 것이 아니라 얼마나 코드를 봤을 때 직관적이고 이해가 쉬운지에 관한 것이다. 따라서 코드를 문서화하는 대신 코드를 깔끔하게 만들어야 한다. 6 | 7 | **깔끔한 코드란?** 8 | 9 | 깔끔한 코드는 단위 테스트를 포함하여 말한다. 단위 테스트를 통해서 해당 코드가 어떻게 동작해야 하는지 보여줄수 있어야 한다. 10 | 11 | ## 의견 12 | 13 | 개인적으로 이번에 브라운이 제공한 Spring 학습 테스트에서 테스트 코드를 보고 도메인을 작성하는 훈련을 통해서 이 부분 챕터에 대해서 많이 느꼈다. 깔끔하게 짜여진 테스트를 통해서 도메인이 어떻게 동작해야 하는지 쉽게 파악할 수 있었다. -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 2.2 최소한 뭔가는 캡슐화하세요 2 | 3 | ### 내용 4 | 5 | * 정적 메서드가 존재하지 않고 인스턴스 생성과 실행을 엄격하게 분리하는 순수한 OOP에서는 기술적으로 프로퍼티가 없는 클래스를 만들 수 없다. 6 | * 객체가 어떤 것도 캡슐화하지 않는다면 객체 자신이 세계 전체가 되어야 한다. 세상에 단 하나만 존재하는 것이어야만 한다. 7 | 8 |
9 | 10 | ### 생각 및 의문점 11 | 12 | 지금까지 내가 해왔던 프로그래밍을 생각해보면 필드 없이 메서드만 존재하는 클래스를 따로 생성한 적은 거의 없었던 것 같다. 유틸리티 적인 클래스 빼고 말이다. 객체지향적으로 바라보았을 때, 유틸리티에 관한 메서드는 옳지 않다는 것에는 동의한다. 하지만, 애플리케이션을 만들고자 하는 것이지 언어를 가지고 객체로 이루어진 퓨어한 집합체를 만드는 예술작업을 하는 것이라고 생각하지 않는다. 필요하다면 쓰는 것이 맞지 않을까 싶다. -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 프로퍼티로 4개 이하의 객체만 포함하라. 2 | - 4개는 저자의 경험칙에서 나온 숫자이다. 상태가 많아지면 유지보수성이 떨어진다. 3 | - 왜일까? 우리의 사고방식이 4이상의 숫자를 다루기에 적절치 않기 때문이라고 생각한다. 4 | 인지과학에서 사람은 5 이하의 숫자는 매우 쉽게 다루고, 그 이상의 숫자는 인지능력을 활용하기 시작한다는 내용을 읽은 적이 있다. 그 내용의 연장선 아닐까 생각한다. 5 | - 최대한 상태를 적게 캡슐화하고, 만약 그 이상의 상태가 생긴다면 객체 분리의 신호로 받아들이면 될 것 같다. 6 | 7 | # 모든 객체의 equals를 구현해라. 상태 == 식별자이다. 8 | - 모든 객체의 equals를 구현하고, 상태가 동일하면 동일 객체로 바라본다는 내용은 저자가 모든 객체를 불변객체로 만드는 변태이기에 가능한 사상 같다. 9 | - 하지만 난 싱글톤도 좋아하고 util클래스도 필요하면 쓰고 static도 필요하면 쓰는데... 흠 10 | -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 2.8 인터페이스를 짧게 유지하고 스마트를 사용하세요. 2 | 클래스를 작게 만드는 것이 중요하다면 인터페이스를 작게 만드는 것은 훨씬 더 중요하다. 왜냐? 클래스가 다수의 인터페이스를 구현하기 때문이다. 두 인터페이스가 5개씩 메서드를 선언한다면, 두 인터페이스를 구현한 클래스는 10개의 public 메서드를 강제로 가진다. 따라서 우리는 Smart 클래스를 통해 이를 해결할 수 있다. 3 | 4 | 공통되지만 경우에 따라 필요할 수도 있고, 아닐 수도 있는 메서드는 인터페이스 내에 Smart 클래스를 통해 해결하자. Smart 클래스는 공통적인 작업을 수행하는 많은 메서드를 포함할 수 있으며, 인터페이스를 구현하는 다른 클래스들안에 동일한 기능을 반복해서 구현하지 않아도 된다. 5 | 6 | ## 의견 7 | 인터페이스 내부에 Smart 클래스를 사용하는 것과 인터페이스를 구현한 Abstract Class를 중간에 사용하는 방법은 어떤 차이점을 가질까? -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/nabom.md: -------------------------------------------------------------------------------- 1 | 스마트 개념은 인터페이스에서 구현자에게 너무 많은 것을 요구하는 것을 방지하기 위한 방법이다. 이전, 페이크 객체와 마찬가지로 인터페이스 내에 스마트 객체를 생성해서 많은 메서드 구현 방식을 줄이는 것이다. 2 | 3 | ```java 4 | interface Exchange { 5 | float rate(String source, String target); 6 | final class Smart { 7 | private final Exchange origin; 8 | public float toUsd(String source) { 9 | return this.origin.rate(source, "USD"); 10 | } 11 | } 12 | } 13 | ``` 14 | 15 | 데코레이터 패턴과 매우 유사하다! 스마트 객체는 결국 인터페이스를 짧게 유지하기 위한 객체이므로 인터페이스를 짧게 유지해야한다는 목적을 기억하자! -------------------------------------------------------------------------------- /3장-취업/5절-절대-getter와-setter를-사용하지-마세요/nabom.md: -------------------------------------------------------------------------------- 1 | 먼저, 객체와 자료구조의 차이를 알아야한다. 2 | 어떤 개성도 지니지 않은 채 단순한 데이터 가방을 자료구조라고 한다. 3 | 자료구조는 getter와 setter를 사용함으로써 캡슐화가 전혀 되어 있지 않다. 4 | 비록 필드 값을 private로 선언한다 해도 실질적으로는 getter와 setter를 사용한다면 5 | 접근이 모두 가능하니 캡슐화가 전혀 되어있지 않다라고 볼 수 있다. get~~ 과 같은 prefix를 6 | 사용하는 것도 있는데 그렇게 되면 내부 구조가 다 오픈이 되는 것이니 좋지 않은 선택이다. 7 | 8 | ### 느낀 점 9 | 완전 동의하는 부분이다. 우테코에서도 getter와 setter를 최대한 지양했다. 10 | 아쉬운 건 많은 라이브러리에서 리플렉션 기법을 사용 할 때 이 prefix를 따르는 것이다! 11 | 그래서 나는 getter와 setter만 사용하는 객체는 DTO만 사용하게끔 하고 도메인 모델은 최대한 지양한다. -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 2.7 문서를 작성하는 대신 테스트를 만드세요 2 | 3 | ## 내용 4 | 5 | * 이상적인 코드는 스스로를 설명하기 때문에 얻던 추가 문서도 필요하지 않다. 6 | * 단위 테스트는 클래스의 사용 방법을 보여주는데 반해, 문서는 이해하고 해석하기 어려운 이야기를 들려준다. 7 | * 메인코드만큼 단위 테스트에도 관심을 기울여라. 8 | 9 |
10 | 11 | ## 생각 및 의문점 12 | 13 | 테스트 코드에 신경을 쓰라는 말에는 동의한다. 그리고 내부 메서드 또한 잘 짜여진 테스트 코드를 보면 이해하기 쉽다는 것 또한 동의한다. 브라운의 스프링 학습 테스트를 보면서 크게 느낄 수 있었다. 그러나 결국에는 문서화는 피할 수 없다고 생각한다. 내부 메서드에 설명이 아니라 api 스펙이라던지 팀의 코드 컨벤션이 결국엔 들어가게 될 것인데, 문서화를 하는 김에 내부 애플리케이션에 대한 설명 또한 문서화하는 것도 그렇게 나쁘다고 생각하지 않는다. 14 | 15 | -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 2.9 인터페이스를 짧게 유지하고 스마트(smart)를 사용하세요 2 | 3 | ## 정리 4 | - 인터페이스를 짧게 만들고, 스마트 클래스를 인터페이스와 함께 배포한다. 👉 공통 기능을 추출하고 코드 중복을 피할 수 있다. 5 | - 인터페이스는 작고 높은 응집도를 유지할 수 있다. 👉 스마트 클래스는 아주 명확하고 공통적인 작업을 수행하는 많은 메소드들을 포함할 수 있기 때문이다. 6 | - 스마트 클래스 ≒ 조합 가능한 데코레이터(composable decorator) 7 | - 스마트 클래스는 클래스에 새로운 메소드를 추가한다. 8 | - 한편, 데코레이터는 클래스에 이미 존재하는 메소드를 좀 더 강력하게 만든다. 9 | 10 | ## 느낀점 11 | 12 | - 스마트 클래스 역시 이번에 처음 알게 됐다. 솔직히 이게 뭔지 잘 와닿지는 않는다. 13 | - 나중에 생각나면 다시 읽어봐야겠다. 그때는 지금보다는 더 이해하겠지..? -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 2.2 최소한 뭔가는 캡슐화하세요 2 | 3 | 프로퍼티가 없는 클래스는 객체 지향에서 악명이 높은 정적 메서드와 유사합니다.
4 | 이런 클래스는 아무런 상태와 식별자를 가지지 않고 오직 행동만을 포함합니다.
5 | 6 |
7 | 8 | 캡슐화된 상태는 세계 안에서 객체의 위치를 지정하는 고유한 식별자입니다.
9 | 객체가 어떤 것도 캡슐화하지 않는다면 객체 자신이 세계 전체가 되어야합니다.
10 | 11 | # 느낀점 12 | 13 | 사실 별 생각 없이 프로퍼티가 없는 클래스(유틸성 클래스)를 많이 사용했었다.
14 | 이 장을 읽고 난 다음, 유틸성 클래스에 대한 고민을 많이 하게 되었다.
15 | 2.1절에선 유틸성 클래스가 뭐가 나쁜지 모르겠다!는 생각이었는데,
16 | 이번 장을 읽으면서는 유틸성 클래스가 반드시 필요한 상황은 없는 걸까? 🤔 라는 생각이 들었다.
17 | 더 많은 코드를 작성해보면서 생각해봐야할 고민이 생겼다.
-------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/suri.md: -------------------------------------------------------------------------------- 1 | # 2.2 최소한 뭔가는 캡슐화 하세요 2 | 3 | - 객체가 무와 아주 비슷한 어떤 것이 아니라면 무언가를 캡슐화 해야 합니다. 4 | - 무 - 오직 하나만 존재하고, 생존이나 자신의 좌표를 표현하기 위해 다른 엔티티를 필요로 하지 않음 5 | - 어떤 일을 수행하는 객체는 다른 객체들과 공존하면서 이들을 사용 6 | - 자기 자신을 식별할 수 있도록 다른 객체들을 캡슐화 해야한다. 7 | 8 | 9 | 10 | # 느낀점 11 | 12 | 2.1 - 가능하면 적게 캡슐화 하세요와 이어지는 내용인 것 같다. 13 | 14 | 적게 캡슐하는 화 하는 것. 그러나 최소한 하나의 식별자가 캡슐화 되어야 하는 것. 15 | 16 | 우아한테크코스에서 받은 물병에는 크루들의 이름이 새겨져 있다. 17 | 18 | 그것이 아니면 모든 상태는 똑같기 때문에 누구의 물병인지 구분 할 수 없다. 19 | 20 | 자기자신을 식별 할 수 있는 이름을 가져서 우리는 우테코 물병을 구분 할 수 있다..! 21 | 22 | -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/suri.md: -------------------------------------------------------------------------------- 1 | # 2.5 퍼블릭 상수를 사용하지 마세요. 2 | 3 | - 객체들은 어떤 것도 공유해서는 안됩니다. 4 | - 독립적이어야 하고, 닫혀 있어야 합니다. (Closed) 5 | 6 | 7 | 8 | - 결합도의 증가 9 | - 상수는 어떤 정보도 제공하지 않은채 모든 곳에서 접근이 가능한 전역 가시성 안에 방치 되어 있다. 10 | - 상수를 수정하게 되었을 때, 어느 곳에서 문제가 생길지 알기 어려움 11 | 12 | 13 | 14 | - 응집도의 저하 15 | - 상수는 자신의 해야할 일을 알고 있지 못함 16 | - 낮은 응집도는 객체가 자신의 문제에 덜 집중하는 것을 의미 17 | 18 | 19 | 20 | - 계약을 통한 결합 21 | - 계약은 유지하면서 동작은 변경 할 수 있다. 22 | 23 | 24 | 25 | # 느낀점 26 | 27 | 이번 챕터를 읽으면서 상수 대신 결합을 사용하는 예시를 보았다. 이렇게 해결하면 OCP를 만족하는 코드가 되는 것 같다...! -------------------------------------------------------------------------------- /4장-은퇴/3절-final이나_abstract이거나/bepoz.md: -------------------------------------------------------------------------------- 1 | # 4.3 final이나 abstract이거나 2 | 3 | ## 내용 4 | 5 | * 상속을 받아 기존의 메서드를 오버라이딩 한 경우 사용자가 부모의 메소드를 사용하듯이 자식에게 똑같은 이름의 메소드를 호출했을 때 부모가 행동하던 것을 기대하지만 다르게 행동하기 때문에 문제의 원인을 찾아내기까지 꽤 오랜 시간이 걸린다. 6 | * 상속은 복잡성이 상승하고 코드를 읽고 이해하기가 굉장히 어려워져 유지보수성을 해친다. 이를 위해, 클래스와 메소드를 final이나 abstract 둘 중 하나로만 제한한다면 문제가 발생할 수 있는 가능성을 없앨 수 있다. 7 | * 상속은 클래스의 행동을 확장하지 않고 정제할 때 사용한다. 8 | 9 | ## 생각 및 의문점 10 | 11 | 이번 장도 공감한다. 확장을 위해서는 인터페이스를 사용하면 된다고 생각한다. 미션을 진행하면서도 abstract이 아닌 클래스를 상속받아 사용한 경우는 없었던 것 같다. 다른 크루들도 이 부분에 대해서 많이 듣고 배운 것이 있어서 공감할거라고 본다. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 엘레강트 오브젝트 스터디 2 | 3 | ## 🐥 시작 날짜 4 | 5 | 2020.04.13 화 (레벨 2 시작일) 6 | 7 | ## 🐥 진행 방법 8 | 9 | 매일(월~금)마다 '2 개 챕터'의 내용을 정리한 것을 공유(인증)한다. 10 | 11 | 정리해야할 내용 : 12 | 13 | - (필수) 간략한 정리 14 | - (필수) 읽고 느낀 점 15 | - (선택) 의문이나 서로 이야기하고 싶은 주제 16 | 17 | 의문이나 서로 이야기하고 싶은 주제는 댓글로 서로의 의견을 남긴다. 18 | 19 | **오프라인 시 (수, 금)** 20 | 21 | 의문이나 서로 이야기하고 싶은 주제가 길어진다면 오프라인에서 해결! 22 | 23 | ## 🐥 공유(인증) 방법 24 | 25 | 각 장, 절에 맞는 폴더에 들어가 본인의 닉네임으로 마크다운을 만들어 거기에 내용을 정리하기. 26 | 27 | 예 ) 나봄은 [nabom.md](http://nabom.md) 파일에 내용을 정리 28 | 29 | 만일, 토론하고 싶은 내용이 있다면 이슈로 남기기 30 | -------------------------------------------------------------------------------- /3장-취업/3절-인자의-값으로-NULL을-절대-허용하지-마세요/joy.md: -------------------------------------------------------------------------------- 1 | # 인자의 값으로 NULL을 절대 허용하지 마세요 2 | ## 정리 3 | null을 사용하면 객체를 존중하지 않고 무시하게 된다. 객체가 자신의 행동을 온전히 책임진다는 객체 패러다임과 상반됨. 4 | 만약 모두가 null을 사용하지 않는다고 합의되었어도 클라이언트가 null을 전달한다면? 5 | 1. 전달된 인자가 null인지 체크한다. 6 | 2. 무시하고 NPE가 발생해도 방치한다. NPE는 메서드 호출자(클라이언트)에게 잘못된 인자를 전달했다고 알려주는 올바른 지표이다. 7 | 8 | ## 느낀점 9 | 100% 공감!! 10 | 메서드 인자는 절대 null을 전달하면 안된다는 건 두 말할 필요도 없고, 11 | null일 경우를 위해 방어적으로 null체크를 할 필요도 없다는 말도 공감한다! 12 | null체크는 있다면 가독성, 유지보수성을 떨어뜨린다. 13 | 한번 null체크를 하는 순간 전염되듯 다른 메서드에도 null체크를 작성하게 될 것이다! 14 | 그걸 방지하려면 시작부터 null체크를 하지 말아야한다. 15 | -------------------------------------------------------------------------------- /3장-취업/7절-인트로스펙션과-캐스팅을-피하세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 3.7 인트로스펙션과 캐스팅을 피하세요 2 | 3 | ## 내용 4 | 5 | * 타입 인트로스펙션은 리플렉션이라는 더 포괄적인 용어가 있다. 리플렉션은 코드를 유지보수하기 어렵게 만든다. 타입에 따라 객체를 차별하기 때문에 OOP의 기본 사상을 훼손시킨다. 차별하지 말고 객체가 누구건 상관없이 자신의 일을 할 수 있도록 해야한다. 6 | * 메서드 오버로딩을 통해서 메서드를 나누는 것이 더 바람직하다. 7 | 8 |
9 | 10 | ## 생각 및 의문점 11 | 12 | ``instanceof`` 연산자는 리뷰어한테 지양하라고 들어본 적이 있는 방법이다. 체스 미션 중에 특정 기물을 체크할 때에 사용했었다. 13 | 개인적으로 추상화를 깨는 행위라고 생각하고 하드코딩에 가깝다고 생각한다. 위 미션에서는 상위 클래스에 메서드로 ``isKing()``을 false를 반환하게끔 해두고 King 기물에서만 오버라이딩하여 true를 반환하는 식으로 처리했던 경험이 있다. ``instanceof`` 사용하면 편하지만 지양하라! 명심하겠다. -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/suri.md: -------------------------------------------------------------------------------- 1 | # 2.4 메서드 이름을 신중하게 선택하세요. 2 | 3 | - 빌더의 이름은 명사로 조정자의 이름은 동사로 지어라 4 | - 빌더란 뭔가를 만들고 새로운 객체를 반환하는 메서드 5 | - 조정자는 객체로 추상화한 실세계 엔티티를 수정하는 메서드 6 | 7 | 8 | 9 | - 뭔가를 조작한 후 반환하거나 뭔가를 만드는 동시에 조작하는 메서드가 있어서는 안됨 10 | - 메서드는 빌더나 조정자 둘중 하나여야 한다. 11 | - Boolean을 반환하는 경우는 이름을 형용사로 지어야 한다. 12 | 13 | 14 | 15 | # 느낀점 16 | 17 | 언젠가 "지금까지 메소드는 동사로 시작해야 한다." 라는 말을 듣고 부터 메소드 이름은 동사로 시작하려고 하였다. 18 | 19 | 그러나 이번 챕터를 읽으면서 빌더와 조정자라는 개념에 대해 알게 되었다. 실생활에서 처럼 정말 객체에게 자율성을 주는 듯한 느낌이 들었다. 20 | 21 | 앞으로는 해당 챕터에서 읽은 내용을 사용해 봐야 겠다. 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 생성자에 코드를 넣지 마세요. 2 | 3 | - 인자에 손대지 말라 4 | 5 | - ctor에 코드가 없을 경우 성능 최적화가 더 쉽기 때문에 코드의 실행 속도가 더 빨라진다. 6 | 7 | - 인자로 전달된 상태 그대로 캡슐화하고 나중에 요청이 있을 때 파싱 하도록 하여라. 8 | 9 | - ctor안에 코드가 있을 경우 후에 리팩토링 하기가 힘들다. 10 | 11 | ```java 12 | class StringAsInteger implements Number{ 13 | private String text; 14 | public StringAsInteger(String txt){ 15 | this.text = txt; 16 | } 17 | public int intValue(){ 18 | return Integer.parseInt(this.text); 19 | } 20 | } 21 | ``` 22 | 23 | 24 | 25 | 예고르 형님 말대로 성능 최적화 하는데에 장점이 있을듯! 담에 써봐야겠군... -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/koda.md: -------------------------------------------------------------------------------- 1 | # 최소한 뭔가는 캡슐화 하세요 2 | 3 | ## 내용 4 | 5 | - 아무것도 캡슐화 하지 않은 객체(프로퍼티가 없는 객체)는 정적 메서드와 유사하고 오직 행동만 포함한다. 6 | - 인스턴스 생성과 실행을 엄격하게 분리(즉, 합당한 객체를 만드는 것과 기능을 제공하는 시점 등을 분리하는 것)하는 객체지향에서는 프로퍼티가 없는 객체는 만들 수 없다. (프로퍼티가 없는 객체는 인스턴스 생성이 적절하지 않기 때문에..?) 7 | - 앞서 객체의 프로퍼티가 해당 객체의 식별자라고 정의했기 때문에 아무런 프로퍼티가 없는 객체는 오직 하나만 존재하여 그 자체가 식별자인 객체밖에 없다고 볼 수 있다. 8 | 9 | 10 | 11 | ## 의견 12 | 13 | - 이전에 구현을 할 때 아무것도 캡슐화 하지 않은 객체는 주로 기능 위주로 분리된 객체가 되고 1.1에서 지양하고자 했든 -er로 끝나는 객체로 만들어지기 쉬웠다. 그래서 작가가 말한 최소한 무언가를 캡슐화 하라는 의미가 해당 객체가 무엇을 하는지가 아니라 무엇인지를 기준으로 이름을 짓고 구현하라고 했던 것과 연결해서 동의가 되었다. 14 | 15 | -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 2.5 퍼블릭 상수를 사용하지 마세요. 2 | 3 | `public static final`인 상수는 객체 사이에 데이터를 공유하기 위해 사용하는 메커니즘이다. 하지만 객체들은 그 어떤 것도 공유해서는 안되고, 독립적이어야하고 닫혀있어야한다. 따라서 상수를 이용한 공유 메커니즘은 캡슐화와 객체 지향적인 사고를 부정한다. 4 | 5 | 따라서 공유되는 상수를 Constants라는 클래스에 public static final로 선언하여 공유할 수 있다. 하지만 이는 결합도를 증가시킬 수 있기 때문에 특정 상수가 하는 행위를 담을 수 있는 클래스를 생성해 활용하는 것이 좋다. 6 | 7 | ### 의견 8 | 상수를 Constants라는 클래스로 분리하는 것은 권장하지 않는다고 하셨다. 클래스 내에서 사용하는 퍼블릭 상수는 최상위 프로퍼티에 위치하도록 해서 내부에서 private하게 사용하는 것이 좋다고 생각한다. 9 | 10 | 그런데 만약, 여기저기서 사용되는 공통 퍼블릭 상수인 경우에는 어떻게 하는 것이 좋을까? 이 부분에 대해서 나는 아직 마땅한 답을 내리지 못한 상태라 다른 크루들의 의견을 듣고 싶다. -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 3.1 5개 이하의 public 메서드만 노출하세요 2 | 3 | ## 내용 4 | 5 | * 클래스의 크기는 public 메서드의 개수로 따지며 적절한 수는 5개다. 6 | * 클래스를 작게 만들면 우아함, 유지보수성, 응집도, 테스트 용이성이 향상된다. 7 | * 각각의 메서드가 객체의 진입점이고 진입점의 수가 적다면 문제를 더 쉽게 고립시킬 수 있기 때문에 유지보수 하기 쉽다. 8 | * 클래스가 작으면 메서드와 프로퍼티가 더 '가까이' 있을 수 있기 때문에 응집도가 높아진다. 9 | 10 | ## 생각 및 의문점 11 | 12 | 이번 장에서 말하는 클래스의 크기는 모든 코드의 양이 아니라 바깥에서 해당 객체를 통해 호출할 수 있는 메서드의 수를 말한다. 사실 private 메서드는 public 메서드 내부의 로직을 얼마나 메서드 분리하냐에 따라 얼마든지 늘어날 수 있기 때문에 public 메서드의 개수로 클래스의 크기를 정한다는 말에 동의한다. public 메서드의 수를 줄이기 위해 노력하다보면 객체가 나뉘게 되고 응집도가 높아지고 더욱 객체지향적으로 설계할 수 있다고 나 또한 인정하는 부분이다. -------------------------------------------------------------------------------- /3장-취업/4절-충성스러우면서-불변이거나,-아니면-상수이거나/joanne.md: -------------------------------------------------------------------------------- 1 | # 3.4 충성스러우면서 불변이거나, 아니면 상수이거나 2 | 3 | 4 | > 불변이라 해서 반환하는 값이 항상 고정된 것은 아니다. 반환 값이 항상 고정된다면 상수라 표현하고 필드, 프로퍼티는 고정되있지만 값이 계속해서 달라질 수 있는 객체를 불변객체라 칭한다. by 나봄 정리글 5 | 6 | 7 | 불변 객체를 사용해야한다. 8 | 하지만 내가 생각한만큼 빡센 불변객체가 아니었다. 9 | 저자가 생각하는 불변 객체는 상태값이 항상 동일한 객체이다. 10 | 11 | 내부 메서드를 통해 새로운 값을 리턴(ex. 책 예제에서의 `content()`)하더라고 어쨌든 그 객체의 상태가 변경되지 않는다면 불변이라면 그것은 불변 객체이다. 12 | 필드로 리스트를 갖는 경우에도, add 메서드를 통해 그 필드에 값을 추가하는 것은 허용된다. (이에 대해서는 그 리스트가 가리키는 주소값이 동일하기 때문인가?) 13 | (*근데 주소만 같으면 불변인가?*) 14 | 15 | 16 | 이 책에서 하도 불변 불변거려서 다음 지하철 미션에는 최대한 모든 객체를 불변으로 만들어 볼 생각이다. 17 | 18 | -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/cu.md: -------------------------------------------------------------------------------- 1 | # 1.3 생성자에 코드를 넣지 마세요 2 | 3 | - 인자에 손대지 말라! 인자를 직접 변환하려 하지말고, 객체를 조합하라 4 | 5 | ```java 6 | class Cash { 7 | private int dollars; 8 | Cash(int dollars) { 9 | this.dollars = Integer.parseInt(dollars); 10 | } 11 | } 12 | ``` 13 | ```java 14 | class Cash { 15 | private Number dollars; 16 | Cash(int dollars) { 17 | this.dollars = new StringAsInteger(dollars); 18 | } 19 | } 20 | ``` 21 | 22 | - 단순히 파싱하는 정도는 Cash의 다양한 생성자를 통해 해결해도 되지 않을까? 너무 많은 객체의 조합이 비즈니스 로직을 이해하는데 저해를 주진 않을까? 23 | - Lazy Loading의 이점과 데코레이터 패턴의 이점은 공감이 가나, 여기서 예로 제시한 캐싱전략에서 Lazy Loading이 갖는 이점은 공감하기 어렵다. 24 | -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/joy.md: -------------------------------------------------------------------------------- 1 | # 5개 이하의 public 메서드만 노출하세요 2 | ## 정리 3 | 클래스의 크기를 결정하는건 public메서드의 개수다. 4 | 클래스를 작게 만들면 여러 장점이 있다. 5 | 1. 실수할 가능성이 줄어든다. 메서드 수가 적을수록 조화를 이루도록 만들기 쉽다.(?) 6 | 2. 유지보수하기 쉽다. 메서드를 클래스의 진입점이다. 진입점이 적을수록 문제를 고립시키기 쉽다. 7 | 3. 메서드와 프로퍼티가 가까이 있게된다. 각 메서드가 클래스의 모든 프로퍼티를 사용하기 때문에 응집도가 올라간다. 8 | 4. 테스트하기 쉽다. 사용 시나리오를 쉽게 재현할 수 있다. 9 | 10 | ## 느낀점 11 | 적극 동의한다. 내 코드를 보니 public메서드 5개 이하인 클래스가 거의 없었다. 그래서 테스트하기가 그렇게 싫었던건가? 앞으로의 미션에서 퍼.메.5 원칙을 지키도록 노력해야겠다. 12 | `메서드 수가 적을수록 조화를 이루도록 만들기 쉽다` <- 이게 무슨 말인지 잘 모르겠다. 객체끼리 조화를 이룬다는 말인가? 실수할 가능성이 적기 때문에 우아하다고 하다가 갑자기 조화를 이룬다는 얘기가 나와서 이해를 못했다. 13 | -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 모의 객체 대신 페이크 객체를 사용하세요 2 | 3 | - 모킹 == 모의객체 4 | - 객체를 잘 설계한다면 모킹이 필요하지 않다. 5 | - 모킹은 실제 구현이 틀려지면, 정상 작동 하지만 테스트는 실패할 가능성이 있다. (유지보수성이 떨어짐) 6 | - 인터페이스에 페이크 클래스를 넣어서 테스트하여라! 7 | 8 | ```java 9 | interface Exchange { 10 | float rate(String target); 11 | float rate(String origin, String target); 12 | final class Fake implements Exchange { 13 | @Override 14 | float rate(String target) { 15 | return this.rate("USD", target); 16 | } 17 | 18 | @Override 19 | float rate(String origin, String target) { 20 | return 1.2345; 21 | } 22 | } 23 | } 24 | ``` -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/koda.md: -------------------------------------------------------------------------------- 1 | # 2.1 가능하면 적게 캡슐화하세요 2 | 3 | ## 내용 4 | 5 | **일단, 모든 것은 유지보수성과 관련이 있다!** 6 | 7 | 가능한 적게 캡슐화 하라는 것은 4개 이하의 객체를 캡슐화 하라는 것이다. (4는 의미없는 숫자) 8 | 9 | 적은 숫자의 객체를 캡슐화 하면 유지보수성이 올라간다. 10 | 11 | 그렇게 캡슐화된 '상태' 또는 '식별자'라고 불리는데 두 객체의 상태가 같다면 같은 객체로 취급해야 한다. 따라서 상태가 없는 객체는 있을 수 없는 객체다. 12 | 13 | 따라서 캡슐화 되어있는 객체는 해당 객체의 식별자 역할을 하므로 적은 수의 객체를 캡슐화 하여 단순화 해야 한다. 14 | 15 | ## 의견 16 | 17 | - 적은 수의 객체를 캡슐화 하고자 하는 의도로 설계를 하면 더 분리를 하도록 고민하게 되고 작가가 언급한 객체 트리를 이루게 된다. 그렇게 조합으로 더 큰 객체를 만드는 것이 객체지향이구나 느껴졌다. 18 | - 상태가 없는 객체는 있을 수 없다고 하는 걸 보면 '기능' 중심의 객체를 반대하는 작가의 일관성이 보였다. 상태가 없는 객체를 구현하게 되면 잘못 설계했다고 생각하면 될까? -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/koda.md: -------------------------------------------------------------------------------- 1 | # 항상 인터페이스를 사용하세요 2 | 3 | ## 내용 4 | 5 | - 객체지향에서 여러 객체들을 서로 필요해서 결합하지만 또 유지보수를 위해서 분리되어야 한다. 6 | - 서로의 필요성을 채워주면서 분리가 될 수 있는 방법 -> 인터페이스 사용 7 | - 인터페이스는 **하나의 객체가 또 다른 객체와 의사소통하기 위한 일종의 계약**이다. 8 | - 인터페이스로 결합도를 낮출 수 있는 이유는, 동일한 인터페이스를 구현하는 여러 객체들이 서로 대체될 수 있기 때문이다. 9 | - 추천하기로는 객체의 모든 public 메소드는 최소한 하나의 인터페이스를 구현해야 한다. 10 | 11 | ## 의견 12 | 13 | - 인터페이스가 지금보다 더 익숙하지 않았을 때는 인터페이스 생성과 구현이 번거롭고, 구현체에서 메서드 리턴타입이나 인자를 바꿀때 연쇄적으로 인터페이스도 모두 수정해야 하는게 불편했다. 14 | - 하지만 더 복잡한 요구사항을 구현하고 이 책을 읽으면서 제이슨이 수업 때 말했던 `플러그`의 역할을 하는 인터페이스의 중요성에 대해서 조금씩 느낄 수 있었다. 15 | - 동일한 인터페이스를 구현하는 구현체간의 일관성을 유지할 수 있도록 해주는 역할을 하기도 한다. -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 2.1 가능한 적게 캡슐화하라 2 | 3 | 예고르씨는 유지 보수성을 위해 최대 4개 그 이하의 객체를 캡슐화할 것을 권장합니다. 4 | 내부에 캡슐화된 객체는 객체의 '상태(state)' 혹은 '식별자(identity)'라고 부릅니다. 5 | 6 | 그렇다고 부품이 없는 객체는 의미가 없기에 다른 객체를 캡슐화하지 않는 객체란 존재하지 않으며 존재할 수도 없다고 말합니다. 7 | **상태가 없는 객체는 존재해선 안되고 상태는 객체의 식별자이어야합니다.** 8 | 객체의 식별자는 세계 안에 객체가 위치하는 좌표인 것입니다. 9 | 10 | 더 많은 객체가 필요하다면, 더 작은 클래스로 분해해야합니다. 11 | 그리고 java의 결함을 해결하기 위해 == 연산자를 사용하지 말고 equals() 메서드를 오버라이드 해야합니다. 12 | 13 | 14 | # 느낀점 15 | 16 | `equals()`에 대해서 공부한 적이 있었는데 17 | 내부 캡슐화된 객체는 상태 혹은 식별자라고 하는 부분에서, `eqauls()`에 대한 의미가 더 잘 와닿았다. 18 | 그리고 이러한 의미에서 유틸성 클래스가 얼마나 애매한 클래스인가를 느낄 수 있었다. (하지만 유틸성 클래스가 나쁜건지는 잘 모르겠다!) -------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 3.2 정적 메서드를 사용하지 마세요 2 | 3 | ## 내용 4 | 5 | * 정적 메서드 대신 객체를 사용해야한다. 6 | * 정적 메서드는 객체 패러다임의 남용이며, 소프트웨어를 유지보수하기 어렵게 만든다. 7 | * 객체지향적으로 생각하기에서 우리는 누가 누구인지만 정의하고 실행 흐름을 제어할 필요가 없다. "is-a" 관계를 가지고 있다. 올바른 객체지향 설계에서는 정적 메서드를 사용해서는 안된다. 8 | * 결론 : static 키워드를 소프트웨어 어디에서도 사용하지마라. 9 | 10 |
11 | 12 | ## 내용 및 의문점 13 | 14 | > 객체지향에는 if, for, switch, while 연산자는 필요 없습니다. 클래스로 구현된 If, For, Switch, While이 필요할 뿐입니다. 15 | 16 | 위의 주장만 보더라도 객체에 대한 광기가 느껴지는 것 같다. 예고르 아저씨는 람다에 대해서는 어떻게 생각할까 ?? 17 | 사용하게되면 의존도가 생기고, 캡슐화가 깨진다고는 나도 생각한다. 이전의 장들에서도 언급했었지만 우리는 예술을 하려는 것이 아니라 애플리케이션을 만드려는 것이다. 융통성 있게 사용해야 편할 때에는 사용하는게 맞지 않을까?? -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 1.1 클래스 이름에 -er을 사용하지 않는다. 2 | 3 | 클래스는 객체의 팩토리(Factory)입니다. 4 | 이 말은 클래스는 객체를 생성, 즉 인스턴스화(instantiate)한다고 할 수 있습니다. 5 | 6 | 클래스를 객체의 능동적인 관리자로 생각해야합니다. 7 | 객체가 살아있는 생명체라면 클래스는 객체의 어머니입니다. 8 | 9 | 클래스의 이름은 무엇을 하는지(what he does)가 아니라 무엇인지(what he is)에 기반해야한다고 합니다. 10 | 예를 들어 dollar의 저장된 금액을 문자열로 바꿔주는 CahsFormatter 클래스가 있다고 가정해보겠습니다. 11 | CashFormatter 클래스는 실제 객체가 아니라 dolloar 데이터를 다루는 절차들의 집합이 됩니다. 12 | 이는 객체 지향이 아닌, 절차지향적인 방식이 될 수 있습니다. 13 | 14 | 연결정치는 단순 정보만 전달하는 반면, 대표자는 스스로 결정을 내리고 행동할 수 있는 자립적인 엔티티입니다. 15 | 객체는 연결장치가 아닌 대표자가 되어야합니다. 16 | 17 | # 느낀점 18 | 19 | 주 생성자를 하나 두고, 부 생성자를 여러개 둠으로써 조금 더 중복을 제거하고 코드의 복잡성을 낮출 수 있다는 것을 배웠다. -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 문서를 작성하는 대신 테스트를 만드세요 2 | - 훌륭한 테스트는 문서화를 대체한다. 3 | - 새로운 기술을 보면 get started부터 보는 편이다. 메뉴얼부터 읽기 시작하는 경우는 거의 없다. 4 | - 테스트는 내 api를 사용하는 개발자(나 자신을 포함하여)에게 튜토리얼을 제공하는 것과 같다. 5 | 6 | # 느낀 점 7 | - 테스트의 장점은 두 말하면 입이 아플 것이다. 난 코드 치매가 있어서 2주만 지나도 내가 친 코드가 처음 본 것처럼 어색하다. 하지만 든든한 테스트 코드가 있다면? 치매는 호전되지 않겠지만 행복한 나날일 것이다. 요즘 테스트 코드를 작성할 때 은근슬쩍 하나씩 건너뛴다. 나 혼자만 아는 비밀인데, 미래의 나와 내 코드를 읽는 사람들에게 몹시 실례이다. 반성하고 [FIRST](https://ssowonny.medium.com/%EC%84%A4%EB%A7%88-%EC%95%84%EC%A7%81%EB%8F%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%91%EC%84%B1-%EC%95%88-%ED%95%98%EC%8B%9C%EB%82%98%EC%9A%94-b54ec61ef91a)를 준수하는 테스트 코드를 작성해야 겠다. 8 | 9 | -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 2.9 인터페이스를 짧게 유지하고 스마트(smart)를 사용하세요 2 | 3 | ## 내용 4 | 5 | * 클래스는 다수의 인터페이스를 구현하기 때문에 인터페이스를 작게 만드는 것은 굉장히 중요하다. 6 | * 인터페이스가 구현자에게 너무 많은 것을 요구해서는 안된다. 그허다고 이를 해결하기 위해서 또 다른 인터페이스를 정의해야할 필요는 없다. 인터페이스 안에 '스마트'클래스를 추가해서 해결할 수 있다. 7 | * 스마트 클래스를 사용해서 공통적인 작업을 수행하는 메서드들을 처리해준다. 8 | * 코드를 추가할 수록 스마트 클래스의 크기는 점점 더 커지겠지만, 해당 인터페이스는 작고, 높은 응집도를 유지할 수 있게된다. 9 | 10 |
11 | 12 | ## 생각 및 의문점 13 | 14 | 인터페이스 내부에 클래스를 두는 것을 나는 이 장에서 처음봤다. 나는 보통 이번 장의 예시와 같이 인터페이스를 사용하는데 공통적인 코드 구현 부분을 처리할 때에는 추상 클래스에서 인터페이스를 받아와준 후에 그 클래스에서 구현을 하고는 했다. 인터페이스 내부에서 클래스를 만들어서 사용하는 방법이 있다는 것은 처음 알았다. 처음보는 방법이라서 그런지 거부감이 든다. 인터페이스에서 저렇게 사용한다고!? 하는 느낌... -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/suri.md: -------------------------------------------------------------------------------- 1 | ## 1.1 ) -er로 끝나는 이름을 사용하지 마세요. 2 | 3 | - 클래스의 이름은 무엇을 하는지가 아니라 무엇인지에 기반 4 | - 객체는 캡슐화 된 데이터의 대표자 5 | - 클래스 이름이 -er로 끝난다면 이 클래스의 인스턴스는 실제로는 객체가 아니라 어떤 데이터를 다루는 절차들의 집합일 뿐 6 | - 올바른 이름을 지으려면 7 | - 클래스의 객체들이 무엇을 캡슐화 할 것인지 관찰 후 적합한 이름을 찾기 8 | - ex) 그는 픽셀이고 스스로 색을 바꿀 수 있어 9 | - 외부에서 객체에게 그 일을 하도록 요청하면, 객체 스스로 무엇을 할 지 결정해야 함 10 | - 객체는 캡슐화 된 데이터의 대표자 11 | 12 | ## 느낀점 13 | 14 | - -er을 붙힌다면 15 | - 아래와 같이 저 행동만 해야 할 것 같은 느낌 16 | - -er이 아니라 parent라면 17 | - 아이 돌봄과 관련된 책임을 수행함하게 해서 응집도를 높일 수 있을 것 같음 18 | - 우유를 준다, 소화를 시켜준다... 19 | 20 | 21 | 22 | ![img](https://blog.kakaocdn.net/dn/ciQi0g/btq2ypcC4B4/QsNcA8jXgbV4scE5kFQipK/img.png) -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/wannte.md: -------------------------------------------------------------------------------- 1 | # 2.3 항상 인터페이스를 사용하세요 2 | 3 | ## 내용 정리 4 | 객체 사이의 강한 결합도가 심각한 문제가 됨. 유지보수성을 높이기 가장 휼륭한 방법이 인터페이스! 5 | 인터페이스에 정의되지 않은 public 메소드를 가지고 있으면 안된다. 이는 당연하게도 다른 클래스로 하여금 강하게 결합하게 만든다. 6 | 7 | 서비스는 계약이자 인터페이스고, 문서화되어있어야한다.(인터페이스의 방식으로) 8 | 서비스의 제공자들(클래스)들이 서로 경쟁한다. 9 | 10 | 쉽게 서로를 대체할 수 있어야하므로, 느슨한 결합을 가지게 된다. 11 | 12 | ## 느낀점 13 | 인터페이스로 기능을 따로 정의해야한다고 들었던 기억이 있고, 이 글이 그 근거를 잘 설명해주고 있다. 14 | 15 | `하지만, 실제 구현에서는 어느 시점에서 인터페이스의 분리를 진행해 나가야할까?` 16 | 17 | 처음 구현을 진행하여 구성도를 그릴 때 부터, 필요한 기능들을 다 작성 문서화해두고, interface부터 만들기를 시작해야할 것인가? 18 | 아니면, 우선 돌아가게끔만 작성을 하고, 추가적으로 대체 가능성이 확인되었을 때, 이를 인터페이스로 분리하는 것이 좋을까? 19 | 20 | 그리고 모든 기능을 인터페이스로 다 분리하는 것이 좋은 구현일까? 21 | -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 2.4 메서드 이름을 신중하게 선택하세요 2 | 3 | ## 정리 4 | - 빌더(builder) 5 | - 이름을 명사로 짓는다. 6 | - 반환타입은 절대 void가 될 수 없다. 항상 무언가를 반환한다. 7 | - ex. float speed(); 8 | - ex. String parsedCell(int x, int y); 9 | - 조정자(manipulator) 10 | - 이름을 동사로 짓는다. 11 | - 반환타입은 항상 void이다. 12 | - ex. void save(String content); 13 | - ex. quicklyPrint(int id); 14 | - 예외: Boolean 값을 결과로 반환하는 경우 15 | - 이름을 형용사로 짓는다. 16 | - 반환타입은 항상 boolean이다. 17 | - ex. boolean empty(); 18 | - ex. boolean readable(); 19 | 20 | ## 느낀점 21 | 22 | - 메소드 이름을 이런 기준으로 선택할 수 있구나 생각할 수 있었던 주제였다. 23 | - 그래도 클린 코드 원칙에 따라 메소드 이름은 동사로 지을 생각이다. -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 2.3 항상 인터페이스를 사용하세요 2 | 3 | ### 내용 4 | 5 | * 객체들은 서로 필요로 하기 때문에 결합된다. 그러나 이것은 유지보수성에 영향을 미친다. 6 | * 인터페이스는 유지보수를 가능하게끔 분리하는 작업을 돕는 훌륭한 도구이다. 7 | * 클래스 안의 모든 퍼블릭 메서드가 인터페이스를 구현하도록 만들어야 한다. 8 | * 인터페이스와 여전히 결합되는 것은 맞지만, 결합은 전체 시스템을 안정적인 상태로 유지할 수 있고, 시스템의 다른 부분이 변경 사항을 알지 못한 채 한 부분을 싨 로 변경하더라도 시스템이 무너지지 않게 유지할 수 있다. 인터페이스는 우리가 전체적인 환경을 구조화된 상태로 유지할 수 있도록 해준다. 9 | 10 |
11 | 12 | ### 생각 및 느낀점 13 | 14 | 동일한 속성을 가진 여러 객체가 존재할 때에 인터페이스를 사용하는 것은 큰 이점이 있다고 생각한다. 인터페이스를 구현하는 객체에서 인터페이스에 선언된 메서드 외의 메서드를 사용한다면 (예로들면, 책에서 언급한 Cash.cents() 와 같은) 오류가 나기 때문에 이러한 규칙들을 지키면서 구현하다보면 확실한 객체로의 철저한 분리를 하는데 더 도움이 될 것이라고 생각한다. 인터페이스를 항상 사용하라는 것은 OOP에 적합한 주장이라고 생각한다. -------------------------------------------------------------------------------- /3장-취업/3절-인자의-값으로-NULL을-절대-허용하지-마세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 3.3 인자의 값으로 NULL을 절대 허용하지 마세요 2 | 3 | ## 정리 4 | - 인자의 값으로 NULL을 허용하면 `mask == null`과 같은 비교문을 사용할 수 밖에 없다. 5 | - `mask == null`은 mask 객체를 피하고 무시하는 방법이다. 6 | - `mask.empty()`처럼 작성하는 게 mask 객체를 존중하는 의사소통 방식이다. 7 | - 중요하지 않은 NULL 확인 로직으로 코드를 오염시켜서는 안된다. 8 | - NPE는 잘못된 위치에 NULL이 전달됐다는 사실을 알려주는 올바른 지표이다. 9 | - 더 똑똑하거나 더 유용한 정보를 제공하도록 만들 필요가 없다. 10 | - 올바른 방식으로 설계된 소프트웨어에는 NULL 참조가 존재해서는 안된다. 11 | - 방어적으로 대응하지 말고 무시해서 JVM에 정의된 표준 방식으로 처리해야 한다. 12 | 13 | ## 느낀점 14 | 15 | - NPE가 발생했을 때 어느 부분에서 NULL 참조가 일어났는지 확인하기 번거로웠던 경험이 있다. 16 | - 그래서 이번 섹션의 전체적인 내용에 동의한다. 17 | - 그럼 자바 코드에서는 NULL 대신 `Optional`을 사용하면 되는 걸까? Optional은 객체니까? -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/gump.md: -------------------------------------------------------------------------------- 1 | # **1.2 생성자 하나를 주 생성자로 만드세요** 2 | 3 | ## **정리** 4 | 5 | ### **2 - 3개의 메서드와 5 -10개의 생성자를 가지는 클래스를 만드세요** 6 | 7 | 메서드의 수보다 생성자의 수가 더 많아야 응집도가 높고 견고한 클래스를 만들 수 있어요. 8 | 9 | ### **주 생성자 1개 나머지는 부생성자로 만드세요** 10 | 11 | ```java 12 | class Cash { 13 | private int dollars; 14 | 15 | Cash(float dollars) { 16 | this((int) dollars); 17 | } 18 | 19 | Cash(String dollars) { 20 | this(Cash.parse(dollars)); 21 | } 22 | 23 | Cash(int dollars) { 24 | this.dollars = dollars; 25 | } 26 | } 27 | ``` 28 | 29 | 중복 코드를 방지하고 설계를 더 간결하게 해줘요. 30 | 31 | ## **의견** 32 | 33 | 이 원칙이 완벽하다면, 왜 자바 구현체들은 적은 수의 생성자와 많은 수의 메서드를 가질까..? -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 3.1 5개 이하의 public 메서드만 노출하라. 2 | 3 | 클래스의 크기를 정하는 기준은 public 메서드(protected 메서드를 포함)의 개수가 몇개인지에 따라 달라진다.
4 | 예고르씨가 생각하기에 적절한 public 메서드의 수는 5개라고 한다.
5 | 단, 여기서 public 메서드의 수는 생성자와 private을 제외한 나머지를 뜻한다.
6 | (꼭 5개라기보다는 아주 작게 만드는 것의 의미가 더 크다고 볼 수 있다.)
7 | 8 |
9 | 10 | 클래스를 작게 만듬으로써 유지보수하기가 쉽기 때문이다.(에러를 찾기 쉽고, 수정하기도 쉽다.)
11 | 모든 메서드와 모든 프로퍼티가 상호작용하는, 즉 응집도가 낮은 클래스가 유지보수하기가 좋다.
12 | 13 |
14 | 15 | 코드의 줄 수는 중요한 기준이 아니라는 말은 공감되지 않았다.
16 | 개인적으로 코드의 줄 수가 긴 코드는 읽기가 어려울 뿐만 아니로 복잡도가 올라가 유지보수하기 쉽지 않다.
17 | 작은 클래스를 유지할 수록 코드 복잡도가 낮아지면서 유지보수하기 쉽다는 것에는 동의한다.
18 | 다음에 코드를 짤 때는 public 메서드가 얼마나 있는 지 신경쓰면서 작성해봐야겠다.
-------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 1-1. -er로 끝나는 이름을 사용하지 마세요. 2 | > 자바의 클래스는 객체의 팩토리다. 3 | 4 | - 여기서 팩토리는, 객체가 단순 코드의 복제에 불과하지 않다는 것을 나타내는 은유이다. 5 | - 클래스는 객체의 능동적인 관리자로서, 객체를 꺼내거나 반환할 수 있는 저장소로 봐야한다. 6 | 7 | ## 올바른 클래스의 작명법 8 | - What he does 가 아닌 What he is로 이름을 지어야 한다. 9 | - 다시 말해 객체는 그의 역량으로 특정지어져야 한다.(What I can do) 10 | - 그러므로, -er(혹은 or)로 시작하는 작명법은 좋지 않다. 행위에 집중하고 있는 명명이기 때문이다. 11 | 12 | *computer, user 등 고유명사가 된 er은 예외이다.* 13 | 14 | ## 클래스는 대표자다 15 | - 객체는 객체의 외부세계와 내부세계를 이어주는 연결장치로 치부하면 안 된다. => 단순히 데이터의 집합체가 아니다. 16 | - 캡슐화 된 정보의 대표자로 봐야한다 17 | - 클래스의 객체들이 무엇을 캡슐화 할 것인지 관찰하고 이 요소들에 붙일 적합한 이름을 찾아야 한다. 18 | 19 | 20 | # 의문점 21 | - What he does와 What I can do는... 같은 말 아닌가? 22 | -------------------------------------------------------------------------------- /4장-은퇴/2절-체크_예외만_던지세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 4.2 체크 예외만 던지세요 2 | 3 | ## 내용 4 | 5 | * 체크 예외는 해롭고 안전하지 않은 메서드를 다루고 있다는 사실을 기억해야 한다. 예외를 상위로 올리거나 잡아야 한다. 언체크 예외는 메서드를 호출하는 쪽에서 어떤 예외가 던져질 지 예상할 수 없다. 6 | * 예외를 다시 던지지 않고 잡아주는 것은 안전하게 실패하기를 하는 것이다. 7 | * 예외를 체이닝함으로써 문제가 발생했다는 사실을 무시하지 않을 수 있다. 8 | * 가장 최상위 수준에서 오직 한 번만 복구하는 방식을 택해야 한다. 9 | * 실패 재시도는 OOP의 코드를 깔끔한 상태로 유지하기 위해 AOP를 적용할 수 있는 현실적이면서 실용적인 예다. 10 | * 하나의 예외 타입이면 충분하다. 11 | 12 |
13 | 14 | ## 생각 및 의문점 15 | 16 | 예외를 간과하지 않고 생각할 수 있게끔 한다는 점에서 취지는 좋다고 생각한다. 17 | 18 | > 이번 섹션에서 이야기하는 대부분의 내용이 여러분의 사고방식을 변화시킬 수 있을지는 몰라도, 실용적이고 현실적인 조언이 되지는 못할 것입니다. 19 | 20 | 이번 장 초입에 나와있는 말처럼 실용적이고 현실적이지는 않은 것 같다. 예외를 처리하는 코드가 최상위 단계에서 덕지덕지 다 붙어있을 것이라고 생각하니 아찔하다. -------------------------------------------------------------------------------- /4장-은퇴/1절-절대_NULL을_반환하지_마세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 절대 NULL을 반환하지 마세요 2 | 3 | # 느낀 점 4 | - 나는 NULL이 밉다. NPE를 유발하니까!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! NPE를 주깁시다 NPE는 나의 원수 5 | - NULL을 보고있으면 맘 한 구석이 불편하고 NULL이 포함된 코드를 작성하면 죄책감이 든다. 6 | - 그래서 NULL혐오 주장을 펼치는 이번 챕터는 처음부터 끝까지 끄덕끄덕 거릴 수 밖에 없었다고 할 뻔 ㅎ 7 | - 다른 주장은 다 옳소 지만 Optional까지 혐오해버리는 저자의 주장엔 동의할 수 없다. 내가 NULL을 미워하는 이유는 NPE때문이다. NPE를 방지할 수 있는 좋은 도구인 Optional 클래스는 날 편안하게 해준다. 8 | - 도대체 왜 Optional을 싫어하는가? 나는 Optional이 포함되어있는 타인의 코드를 보면 신뢰가 생긴다. 아 이 개발자는 NULL을 미워하는 동지구나! 9 | - Optional이 비록 NULL체크를 클래스화한 것에 불과하더라도 `필요한 곳` 에서만 할 수 있게 해주니 매우 훌륭한 도구라고 생각한다. 객체가 살아있다고 생각하는 저자는 아마 도구 안에 객체가 갇혀있는 느낌이 드는 모양이다. Optional은 없을 수 도 있다 는 개념을 null참조보다 훨씬 우아하게 잘 표현해준다. 앞으로도 잘 써먹을 예정이다. 10 | -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/wannte.md: -------------------------------------------------------------------------------- 1 | # -er로 끝나는 이름을 사용하지 마세요 2 | 3 | ## 내용 정리 4 | 5 | Class는 객체의 템플릿이 아니다.(수동적으로 복사되는 멍청한 코드 덩어리가 아니다) 6 | 7 | Class는 객체의 팩토리다. (능동적인 위치에서 꺼내거나 반환할 수 있다) 8 | 9 | 객체의 기능에 기반해서 이름을 지으면 안된다.(이를 저자는 -er로 끝나는 이름을 사용하지 마세요 라고 자극적이게 표현하고 있다.) 10 | 11 | 객체의 역량으로 특정지어야 한다. 12 | 13 | -er로 이름을 지으면 이는 어떤 데이터를 다루는 절차들의 집합일 뿐이다. (~하는 놈, ~하는 놈, ~하는 놈의 연속이라 생각해보면, 절차들의 연속이 맞는것 같다. 설득당하고 있다.) 14 | 15 | ## 읽고 느낀 점 16 | 17 | 객체의 이름을 지어줄 때에도 의인화를 해서 객체에 기분을 생각하며 진행해보면 어떨까 한다. 18 | 19 | "너는 CashFormat 하는 놈이야. format 하셈;" →기분 so bad할 것 같다. 20 | 21 | "너는 Cash 라는 놈이야. usd 작업 ㄱㄱ", → 뭔가 인정 받은 느낌이다. usd 작업을 열심히 하고 싶어진다. 나의 가능성은 무한하다는 느낌이 든다. 22 | 23 | 맞짱 뜰 각오로 책을 읽기 시작했으나, 설등당한다. 빠져들어간다. 24 | -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/gump.md: -------------------------------------------------------------------------------- 1 | # **2.2 -최소한 뭔가는 캡슐화하세요** 2 | 3 | ## **정리** 4 | 5 | ### **객체가 '무'와 아주 비슷한 어떤 것이 아니라면 무언가를 캡슐화 해야해요** 6 | 7 | '무'란 세계 안에서 좌표가 없는 존재를 의미해요. 이는 오직 하나만 존재하고 생존이나 자신의 좌표를 표현하기 위해 다른 엔티티를 필요로 하지 않는 객체에요. 8 | 9 | ```java 10 | class Universe { 11 | } 12 | ``` 13 | 14 | 오직 하나의 세계만 존재하기에. 이 클래스는 오직 하나만 존재해야 해요 ( 존재의 의미를 찾기 힘드니 일단 넘어가요 ) 15 | 16 | ### **여튼 뭐든 캡슐화 해요.** 17 | 18 | 위와 같은 이유가 아니라면 캡슐화가 되지 않은 객체는 의미가 없어요 ( 유틸성 클래스에 대해서는 이후에 다뤄본다고 해요 ) 19 | 20 | ## **의견** 21 | 22 | 캡슐화를 하지않는 객체를 만들경우, 묵시적 기본 생성자를 사용하는 경우가 많았어요. 이런 경우엔, 명시적으로 지정해주는 게 좋다는 것 정도는 알고있었어요. 23 | 24 | 하지만 캡슐화 하지 않은 객체는 만들지로 말라는 말이 정말 충격적이네요. 생각해보면 정말, `캡슐화가 되지 않은 객체는 필요없는 경우가 많았던 거 같아`요. 25 | 26 | 항상 유의해야할 지식인 거 같아요. -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 2.4 메서드 이름을 신중하게 선택하세요. 2 | 3 | ### 빌더 4 | - 뭔가를 만들고 새로운 객체를 반환하는 메서드 5 | - 빌더의 이름은 항상 명사이어야 한다. 6 | - 형용사를 이용해 의미를 더할 수 있다. 7 | - ex. int pow(); float speed(); 8 | 9 | ### 조정자 10 | - 객체로 추상화한 실세계 엔티티를 수정하는 메서드 11 | - 반환 타입은 항상 void, 이름은 항상 동사 12 | - ex. void save(String content); void put(String key, Float value); 13 | 14 | ### Boolean을 반환하는 경우 15 | - 반환값이 있기 때문에 빌더이지만, 이름은 형용사 16 | - 대부분 isEmpty와 같이 지으나, 읽을 때 is를 붙이고 실제 메서드명은 empty로 지어야한다. 17 | 18 | 19 | ### 의견 20 | 나머지는 그렇다 치고, Boolean을 반환할 때 empty로 지어야하는 건 좀 ? 이건 PK와 전에 이야기해봤을 때 핔 의견이 맞는 것 같아 인용하자면 `메서드명을 보고 true/false로 대답할 수 있는가?`를 기준으로 짓는 것이 좋은 것 같다! 21 | 22 | 그리고 불변객체의 경우! 불변 객체에서는 조정자가 새로운 객체를 반환한다. 그럴 땐 어떻게 하라는겨! -------------------------------------------------------------------------------- /3장-취업/4절-충성스러우면서-불변이거나,-아니면-상수이거나/joy.md: -------------------------------------------------------------------------------- 1 | # 충성스러우면서 불변이거나, 아니면 상수이거나 2 | ## 정리 3 | 불변 객체와 상수 객체는 다르다. '불변'이 '상수'를 포함하는 더 큰 개념이다. 4 | 객체가 살아있는 동안 상태가 변하지 않는다면 불변 객체이다. 5 | `String`, `Double` 등의 객체는 불변이면서 상수 객체다. 상태가 변하지 않으면서 항상 같은 값을 반환한다. 6 | 하지만 반환하는 값이 매번 달라져도 상태가 변하지 않는다면 불변이다. 7 | 8 | 불변객체는 자신이 대표하는 엔티티에 충성스러워야 한다. 객체는 실세계의 엔티티의 대표자이다. 9 | 10 | ## 느낀점 11 | 지금까지 내가 생각했던 '불변 객체'의 정의가 사실 '상수 객체'였다는 사실이 충격적이다. 12 | `ConstantList`, `ImmutableList`를 통해 설명한 내용에 따르면 리스트 내부의 원소가 추가되어도 불변이라고 한다. 나는 리스트의 원소가 추가되는 것도 상태가 바뀌는 거라고 이해하고 있었는데; 13 | 물론 '불변'에 대한 저자의 정의와 나의 정의가 정확히 같을 필요는 없다고 생각하지만 쪼끔 충격... 14 | 그렇다면 그동안 우테코에서 화두였던 일급컬렉션이 불변이어야 하는가 라는 질문은 처음부터 잘못되었던 것인가?😱 우리는 불변성을 보장하려면 원소가 추가될 때 마다 새로운 일급컬렉션을 반환해야한다고 생각했는데 말이지; 15 | -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/joy.md: -------------------------------------------------------------------------------- 1 | # 1.2 생성자 하나를 주 생성자로 만드세요 2 | ## 내용 정리 3 | 클래스는 2~3개의 메서드, 5~10개의 생성자를 포함하는 것이 적당. 4 | 1. 메서드가 많으면 클래스의 초점이 흐려지고 단일 책임 원칙을 위반하게 된다. 5 | 2. 생성자가 많으면 사용자 입장에서 클래스를 더 편하게 사용할 수 있다. 유연성 향상. 6 | 7 |
8 | 9 | 생성자 하나를 주 생성자로 만들고 나머지는 부 생성자로 만든다. 주 생성자는 인자를 통해 객체의 **프로퍼티를 초기화**한다. 부 생성자들은 주 생성자를 호출한다. 10 | 1. 오직 한 곳에서만 프로퍼티가 초기화되어야 한다. 11 | 2. 다른 곳에서는 인자를 준비하고, 포맷팅하고, 파싱하고, 변환만 해야한다. 12 | 코드의 복잡성 줄이고, 중복코드를 방지하여 결국 유지보수성이 향상됨. 13 | 14 | ## 의견 15 | - 주, 부 생성자를 나누는 방식엔 완전 동의! 유지보수성도 좋아지고 설계를 간결하게 유지할 수 있다는 것도 동의! 16 | - 주 생성자를 가장 마지막에 배치하는 것이 과연 가독성이 좋을까? 17 | - 호출되는 순서에 따라 스크롤을 내리다보면 마지막에 위치 vs 맨 위에서 한번에 초기화 로직을 볼 수 있음 18 | - 다른 메서드들은 호출되는 순서대로 나열했는데 왠지 생성자는 프로퍼티 초기화에 관련된 거니 프로퍼티(필드) 19 | -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/koda.md: -------------------------------------------------------------------------------- 1 | # 1.3 생성자에 코드를 넣지 마세요 2 | 3 | ## 내용 4 | 5 | 앞서 하나의 주 생성자와 여러 부생성자를 만들어서 객체가 유연하게 활용될 수 있도록 하라는 챕터가 있었다. 이번 챕터는 그 주생성자에 아무 코드를 넣지 말라는 취지의 글이다. 그 이유는 다음으로 요약할 수 있다. 6 | 7 | > 가벼운 ctor은 설정하기 쉽고 투명하게 사용할 수 있기 때문에 객체를 더 빠르게 만들 수 있다는 점이 바로 그것입니다. 8 | 9 | 주 생성자에 코드를 넣지 말고 오직 `인스턴스화` 하는 작업만 하자! 이후에 파싱이 필요한 작업의 경우는 lazy 하게 처리하는 것이 좋다. 10 | 11 | - 직접 제어하며 파싱을 하므로 최적화가 가능하다. 12 | 13 | ## 의견 14 | 15 | - 필자가 말한대로 주 생성자에 코드를 넣지 않고 파싱의 경우 이후 필요한 시점에서 파싱을 하는 것이 주도적으로 최적화가 가능하다는 점에서 좋다고 생각한다. 이런 식의 코드 흐름은 한번도 생각해 본 적이 없는데 이번에 처음 이런 코드를 보면서 생각이 트인 것 같다. 16 | 17 | ## 의문 18 | 19 | - 그럼 검증 메소드 같은 경우는 어디서 수행하면 좋을까? 검증 메소드는 외부에서? 그렇지만 일급컬렉션의 이점 중 하나는 스스로 검증을 하면서 값을 보장할 수 있는 것이라고 생각하는데 생성자에서 하지 않으면 어디서 해야 할까? -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 5개 이하의 public 메서드만 노출하세요. 2 | 클래스의 크기를 정하는 기준으로 public(protected도 동일) 메서드의 개수를 사용하길 권장한다. public 메서드가 많을수록 클래스의 응집도가 낮아진다. public 메서드가 많다면 다른 클래스와 조화를 이루는 것이 어려워진다. public 메서드를 5개 이하로 줄이고, 나머지를 private 메소드를 이용해 구현하고, 그래도 안된다면, 클래스를 분리하자. 3 | 4 | public 메서드의 개수가 줄어들게 된다면 그 클래스는 테스트하기에도 용이해진다. 또 다른 이유를 들어보자. 만약 클래스의 프로퍼티들이 각각 서로 다른 메서드에서 분리되어 상호작용한다면 이는 서로 관계없는 프로퍼티를 뭉뜽그려 한 클래스안에 모아두었다고 볼 수 있으므로, 좋지 않다. 그러므로 public 메서드 개수를 줄임으로서 한 메서드가 해당 클래스의 모든 프로퍼티와 상호작용할 가능성이 더 높아진다. 5 | 6 | 7 | ### Comment 8 | 퍼블릭 메서드가 많으면 테스트를 애초에 짜기가 싫어진다. 또한 퍼블릭 메서드가 많으면 그 클래스를 관리하는 것이 어렵다. 프로젝트가 점점 커지면 클래스 내에 수많은 퍼블릭 메서드가 어디에서 사용되는 메서드인지 파악하는 것도 어려워진다. 퍼블릭 메서드의 개수를 줄이고, 정 안된다면 클래스를 쪼개보자. (이런 걸 쓸 수록 점점 내 체스코드를 다 뜯어고치고 싶어진다.) -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/suri.md: -------------------------------------------------------------------------------- 1 | ## 1.2) 생성자 하나를 주 생성자로 만드세요. 2 | 3 | 4 | 5 | - 하나의 주 생성자와 다수의 부 생성자 6 | - 생성자의 주된 역할은 제공된 인자를 사용해서 캡슐화하고 있는 프로퍼티를 초기화하는 일 7 | - 이런 초기화 로직을 단 하나의 주 생성자에 위치 시키고, 부 생성자에서는 주 생성자를 호출하도록 만든다. 8 | 9 | 10 | 11 | ```java 12 | class Cash { 13 | private int dollars; 14 | 15 | Cash(float dlr) { 16 | this((int) dlr); 17 | } 18 | 19 | Cash(String dlr) { 20 | this(Cash.parse(dlr)); 21 | } 22 | 23 | Cash(int dlr) { 24 | this.dollars = dlr; 25 | } 26 | 27 | } 28 | ``` 29 | 30 | 31 | 32 | - 위와 같이 구현한다면 주 생성자에만 유효성 검사 로직이 들어가면 된다. 33 | - 유지보수성을 저해하는 가장 커다란 장애물인 복잡성과 중복이라는 두가지 문제가 해결 34 | 35 | 36 | 37 | ## 느낀점 38 | 39 | - '원시값을 포장하라' 객체 지향 생활체조 원칙을 지킬 때 유용하게 사용 할 수 있을 것 같다. -------------------------------------------------------------------------------- /3장-취업/5절-절대-getter와-setter를-사용하지-마세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 3.5 절대 getter와 setter를 사용하지 마세요. 2 | 3 | setter가 존재하는 클래스는 가변 클래스이므로 불변 객체의 원칙부터 깨진다.
4 | 이뿐만 아니라 getter/setter가 존재하는 클래스는 생성자를 가지지 않아도 되는데, 이 클래스를 단순한 자료구조가 된다.
5 | 6 |
7 | 8 | ### 객체와 자료구조의 차이는 뭘까? 9 | 10 | 예고르는 자료구조의 예시로 struct를 이야기한다.
11 | 자료구조는 struct와 같이 아무런 의사소통하지 않고 멤버에 접근하며, 어떤 '개성(personality)'도 지니지 않는 단순한 데이터이다.
13 | 14 |
15 | 16 | OOP가 등장한 이후 코드는 수동적인 존재가 되었지만, 데이터는 능동적인 존재가 되었다. ('객체는 살아있다')
17 | 그럼에도 불구하고 getter는 사용해야할 경우는 많이 있다.
18 | getXX는 "XX를 찾아 반환해라"는 의미가 될 수 있기에, getName()보다는 name()을 사용해야한다.
19 | 20 |
21 | 22 | 이번장에서는 의문이 들 것이 없었다. getter/setter를 지양해야하는 것에 전적으로 동의한다.
-------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 2.5 퍼블릭 상수를 사용하지 마세요 2 | 3 | ## 내용 4 | 5 | * 객체들은 어떤 것도 공유해서는 안되고 독립적이어야하고 닫혀 있어야 한다. 이러한 관점에서 상수를 이용한 공유 메커니즘은 캡슐화와 객체지향적인 사고 전체를 부정하는 일이다. 6 | * 코드 중복이라는 하나의 문제를 해결하기위해 상수를 사용했다가 결합도를 높이고 응집도가 낮아지는 결과를 갖게 된다. 7 | * 퍼블릭 상수마다 계약의 의미를 캡슐화하는 새로운 클래스를 만들어야 한다. 8 | 9 |
10 | 11 | ## 생각 및 의문점 12 | 13 | 상수를 선언하는 것보다 각 객체에 동일한 필드를 선언하는 것이 정말 더 이득인 것인지에 대해 의심스럽다. 만약 해당 값에 대한 처리가 바뀌게 된 경우 모든 객체를 수정하게 될 것인데 말이다. 그리고 62페이지의 코드를 보면 저렇게 직접 입력하는 것은 하드코딩인데 정말 상수화와 비교해서 눈에 띄게 좋은것일까?? 63 쪽이 2번째 문단에 대해서도 유지보수는 좋고 이해하기 편하다는 것은 납득이 간다. 하지만, 개발자가 해당 시스템을 접할 때에 객체의 양에 압도될 것 같다. 파악하는데도 시간이 더 걸릴 것이고 말이다. OOP에서 퍼블릭 상수를 절대로 사용해서는 안된다고 마지막에 언급한다. 그런데, 쓰라고 만들어준 것을 무조건 쓰지 말라 하는 것이 과연 맞는 것일까??? 이상과 현실은 다르다고 생각한다. 트레이드오프를 생각해서 적절히 선택하는 것이 현명하다고 본다. 14 | 15 | -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/wedge.md: -------------------------------------------------------------------------------- 1 | # Mock 객체 말고 Fake 객체 2 | - 처음에 읽을 땐 인터페이스 내부에 Final Class를 구현해놓은 방식 자체가 생소해서 거기에 집중했다. 다시 읽어보니 요점은 방식과 상관없이 Fake객체를 구현하라는 내용이라고 생각한다. 또 다시 읽어보니 인터페이스에 구현하라는 말이 맞네 ㅋㅋㅋㅋ 3 | 4 | # 느낀 점 5 | - 단위테스트를 만들다 보면 두번 이상 활용되는 테스트 케이스를 위해 단위테스트에 fixture라는 패키지와 Fake객체를 생성하는 일이 생긴다. (포비가 익명클래스를 활용하여 Fake객체를 만들어 사용하는 것을 보고 좋은 practice라고 느꼈다) 6 | 7 | - 하지만 오로지 단위테스트를 위한 로직이 생겨서는 안 된다는 원칙은, 의미는 다르지만 Fake 구현체가 인터페이스와 함께 제공되는 것도 불편하게 만든다. 8 | 9 | - 단위테스트가 클래스의 일부라는 저자의 사상에 깊이 공감한다면 Fake객체가 인터페이스와 함께 제공되는 받아들일 수 있게 될거라 생각한다. 10 | 11 | - 나? 나는...... 어느정도 공감했다. 다음 미션때는 페어를 설득해 Fake객체를 활용해보아야 겠다 12 | 13 | - 페어를 설득할 수 있는 미세팁으로 책에 나온 패턴을 그대로 사용하여 인터페이스에 final class를 선언해 페어에게 보여줘라. 아마 페어는 처음보는 패턴에 정신이 아찔하여 이해하는 데 모든 에너지를 쏟느라 논쟁할 힘을 잃을 것이다 14 | -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 2.2 최소한 뭔가는 캡슐화하세요 2 | 3 | ## 정리 4 | - 아무것도 캡슐화하지 않는 방식은 바람직하지 않다. 5 | 6 | ``` 7 | class Year { 8 | int read() { 9 | return System.currentTimeMillis() / (1000 * 60 * 60 * 24 * 30 * 12) - 1970; 10 | } 11 | } 12 | ``` 13 |
14 | 15 | - 순수한 OOP에서는 정적 메소드가 존재하지 않기 때문에 정적 메소드를 호출하는 일이 불가능하다. 16 | - 대신 어떤 클래스의 인스턴스를 생성한 후 이 인스턴스를 통해 값을 얻어야 한다. 17 | 18 | ``` 19 | class Year { 20 | private Millis millis; 21 | 22 | Year(Millis msec) { 23 | this.millis = msec; 24 | } 25 | 26 | int read() { 27 | return this.millis.read() / (1000 * 60 * 60 * 24 * 30 * 12) - 1970; 28 | } 29 | } 30 | ``` 31 | 32 | ## 느낀점 33 | 34 | - 유틸성 클래스는 객체지향을 위반한다고 생각한다. 35 | - 객체는 다른 객체와 협력하기 때문에 최소한 하나라도 캡슐화해야 한다는 의견에 동의한다. -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/koda.md: -------------------------------------------------------------------------------- 1 | # 3.1 5개 이하의 public 메서드만 노출하세요 2 | 3 | ## 내용 4 | 5 | 작은 객체를 유지하는 이유는 1) 우아하고 2) 유지보수가 가능하고 3) 응집력이 높고 4) 테스트하기 용이 하기 때문이다. 6 | 7 | 작은 객체를 말할 때 해당 객체의 라인수보다 pulic 메서드의 개수가 중요하다. 예고르는 5개 이하의 public 메서드가 적당하다고 주장한다. 8 | 9 | **1) 작은 객체의 우아하다.** 10 | 11 | - 작을수록 실수할 가능성이 줄어든다. 12 | - 더 적은 수의 메서드들로 조화를 이루기가 더 쉽다. 13 | 14 | **2) 작은 객체는 유지보수하기 쉽다.** 15 | 16 | - 코드양이 적고 에러를 찾기 쉽고 수정하기가 쉽다. 17 | 18 | **3) 작은 객체는 응집도가 높다.** 19 | 20 | - 메서드와 프로퍼티가 더 가까이 있다. 21 | - 이 말은 각 메서드가 클래스의 모든 프로퍼티를 사용한다는 것이다. (즉, 모두 서로 상호작용한다) 22 | 23 | **4) 작은 객체는 테스트하기 쉽다.** 24 | 25 | - 모든 사용 시나리오를 재현하기 쉽다. 26 | 27 | ## 의견 및 의문 28 | 29 | - 딱히 반박할 부분 없이 동의가 되는 내용이다. 작은 객체가 확실히 보기에도 좋고 유지보수나 테스트 측면에서 좋았다. 30 | - 응집도에 관련해서 메서드와 프로퍼티의 상호작용 정도에 대해서 말한것이 흥미로웠다. 31 | -------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/nabom.md: -------------------------------------------------------------------------------- 1 | 객체 지향은 객체간의 사이의 관계로 구성되는 자연스러운 사고 패러다임이다. 정적 메서드는 이런 관계를 무시한 채 명령을 내리는 방식이다. 정적 메서드를 사용하게 되면 이전에도 이야기했듯이 부가기능을 추가하기가 힘들다. 또한, lazy-loading과 같은 기법을 적용하기도 힘들다. 2 | 3 | 선언형 방식은 객체지향적이다. 선언형 방식의 장점들은 다형성, 표현력, 응집도와 같은 장점들이 있어 유지보수에 용이하다. 하지만 명령형 방식의 정적 메서드는 추가적인 유지보수가 매우 힘들다. 그렇다면 정적 메서드의 집합체인 유틸리티 클래스는 어떠할까? 유틸리티 클래스도 OOP입장에서 본다면 끔찍한 안티 패턴이다. 4 | 5 | 그렇다면 싱글톤 패턴은 어떨까? 싱글톤 패턴도 객체지향 패러다임을 잘못 사용한 예이다. 싱글톤 패턴의 객체를 생성할 당시 유연하게 의존성을 주입하기가 힘들다. 6 | 7 | ###느낀점 8 | 객체간의 관계를 무시한 채 명령을 내리는 방식에는 동의한다. 또한 정적메서드를 사용하게 되면 부가기능(레이지로딩)을 누리기가 힘들어 피하는 것이 맞는 것 같다. 9 | 10 | 하지만 유틸리티 클래스(정적 메서드가 아닌 일반 메서드로 선언되어있는)는... 포기하기 힘든 것 같다. 많은 곳에서 반복이 되는 작업, 예를 들어 Json <-> Java Object Converter(Object Mapper)와 같이 행동 자체에 포커싱이 되어 있는 유틸리티 클래스.. 이러한 클래스가 없다면 어떻게 개발을 할까!!!!!!!! 11 | -------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/suri.md: -------------------------------------------------------------------------------- 1 | # 정적 메서드를 사용하지 마세요 2 | 3 | 4 | 5 | 6 | 7 | - 제 관점에서 이 코드는 매우 깔끔하면서 객체지향 적 8 | - 객체를 어떻게 만들었는지 전혀 설명하지 않고도 이 객체가 무엇인지 할 수 있다. 9 | 10 | ```java 11 | names = new Sorted( 12 | new Unique( 13 | new Capitalized( 14 | new FileNames( 15 | new Directory( 16 | "/var/users/*.xml"), 17 | "([^.]+)\\.xml", "$1" 18 | ) 19 | ) 20 | ) 21 | ); 22 | ``` 23 | 24 | 25 | 26 | - 정적메서드는 조합이 불가능하다. 27 | - 정적 메서드를 포함하는 작은 객체 들을 조합해서 더 큰 객체를 만들 수 없다. 28 | 29 | 30 | 31 | 32 | 33 | ## 느낀 점 34 | 35 | - 객체의 생성과 관련된 정적 팩토리 메소드는 예외라고 생각된다. 36 | - 객체를 반환하니, 조합이 가능 37 | - 조합이 불가능한 static 메소드, 싱글톤 패턴은 사용할 때 한번 더 생각을 해볼 것 같다. 38 | - 결합도가 높아진다는 단점 때문에 39 | - 그래도 싱글톤 패턴 또한 여러 시행착오에 걸쳐 나온 패턴이기에 때에 따라서 사용해도 된다고 생각한다. 40 | - 뭐든 다 장,단점이 있는 것 같다 -------------------------------------------------------------------------------- /4장-은퇴/1절-절대_NULL을_반환하지_마세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 4.1 절대 NULL을 반환하지 마세요. 2 | 3 | 리턴값으로 널을 던진다는 것은, 받는 곳에서 그 값이 널인지 아닌지 매번 확인해줘야하는 것을 의미한다. 이는 객체에 대한 신뢰성을 떨어트리며 불필요한 Null 확인 코드를 모든 메서드마다 추가해야한다. 4 | 5 | 또한, 소프트웨어 견고성과 실패 탄력회복성과 관련해서 상반되는 철학(빠르게 실패하기 vs 안전하게 실패하기) 중 빠르게 실패하기의 지지자인 저자는 버그, 입출력 문제 등이 발생한 상황에서도 소프트웨어가 계속 실행될 수 있도록 최대한 많은 노력을 기울일 것을 권장하는 안전하게 실패하기 보다는 **문제가 발생하면 곧바로 실행을 중단하고 최대한 빨리 예외를 던지는 빠르게 실패하기**를 주장한다. 따라서 null을 반환하고 널인경우 다르게 처리하는 것이 아닌, null일 경우에는 바로 nullPointerException이 던져지도록! 하는 것이 맞다고 주장한다. 6 | 7 | ## 의견 8 | 나 또한 빠르게 실패하기를 선호한다. 오류가 발생하는 프로그램은 중단되어야 맞다고 생각하며 그래야 더 빨리 오류를 해결할 수 있다고 생각한다. 하지만 실제 애플리케이션을 운영하는 상황에서 빠르게 실패하기를 적용하려면 로그에 남기는 것으로 해야하는지? 잘 모르겠다. 9 | 10 | 또한 널과 관련된 이야기에서는 나 또한 메서드 내부에서는 널 체크를 하지 않고 넘어온 객체를 신뢰하고 로직을 수행하는 것이 맞다고 생각하므로, 리턴으로 널을 보내는 것도 올바르지 않다고 생각한다. -------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/joy.md: -------------------------------------------------------------------------------- 1 | # 정적 메서드를 사용하지 마세요 2 | ## 정리 3 | ### 객체 대 컴퓨터 사고 4 | ### 선언형 스타일 대 명령형 스타일 5 | 최적화가 가능함: cpu에게 결과가 필요한 시점과 위치를 결정하도록 위임, 요청이 있을 경우만 계산! 6 | 다형성: 객체 사이의 결합도를 낮출 수 있음. 7 | 표현력: 명령형은 결과를 예상하기 위해 먼저 머릿속에 코드를 실행해야 한다. 세부사항은 감추고 행동만 표현하는 선언형이 더 가독성이 좋다. 8 | 응집도: 서로 연관성있는 코드를 응집시켜서 실수로라도 분리시킬 수 없게 한다. '시간적인 결합' 문제를 해결할 수 있음. 9 | 10 | ### 유틸리티 클래스 11 | 인스턴스를 만들지 않기 때문에 `객체의 팩토리`가 아니며 진짜 클래스라고 볼 수 없다. 12 | 절대 쓰면 안됨. 13 | 14 | ### 싱글턴 패턴 15 | 유틸리티 클래스(정적 클래스)와 비교해서 조금 더 낫지만 그래도 안티패턴임. 16 | 그럼 어플리케이션 전체 클래스에서 사용해야하는 기능은 어떻게 구현? 캡슐화로 해결! 17 | 해당 기능이 필요한 모든 객체의 안에 그 기능(객체)를 캡슐화. 18 | 19 | 싱글턴 절대 쓰면 안됨. 20 | 21 | ## 느낀점 22 | `static`에 대해 할 말이 정말 많았나보다... 23 | 처음엔 static이 암처럼 퍼진다고 앞에서 설명했는데 뒤에선 객체로 static을 고립시키면 된다고 해서 띠용? 24 | 쉽게 고립시킬 수 있다면 static 쓰는걸 너무 무서워하지 않아도 되지 않을까? 25 | -------------------------------------------------------------------------------- /4장-은퇴/1절-절대_NULL을_반환하지_마세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 4.1 절대 NULL을 반환하지 마세요 2 | 3 | ## 내용 4 | 5 | * 한 메서드가 널을 반환하게 된다면 사용자는 그 메서드의 결과값을 사용할 때마다 NPE가 던져질지 모른다는 사실에 불안하게된다. 6 | * 객체에게 아무런 말도 하지 않은 채 우리 마음대로 예외를 던져서응 안된다. 7 | * 신뢰의 부족은 유지보수성의 손실로 이어진다. 8 | * 안전하게 실패하기는 이 상황을 구조하기 위해 노력하고, 빠르게 실패하기는 가능한 실패를 분명하게 만든다. 9 | * 메서드를 2개로 나누거나, Optional을 쓰거나 널 객체를 쓰는 대안법이 있다. 10 | 11 |
12 | 13 | ## 생각 및 의문점 14 | 15 | 나 또한 널을 반환하면 안된다고 생각한다. 16 | 17 | > 반환값을 검사하는 방식은 애플리케이션에 대한 신뢰가 부족하다는 명백한 신호입니다. 18 | 19 | 그렇다면 null이 아닌 Optional.Empty()를 반환하고 이를 검사하는 과정 또한 애플리케이션에 대한 신뢰가 부족하다는 뜻인가?? 20 | 21 | 빠르게 실패하기 vs 안전하게 실패하기에 대해서는 빠르게 실패하기를 더 선호하지만, 어떤 서비스냐에 따라 다르다고 생각한다. 터지면 안되고 최대한 끌고나가야 하는 그런 민감한 서비스면 안전하게 실패하기를 택할 것 같다. 22 | 23 | 마지막에 널 객체가 나오는데 책에서 Optional은 일종의 봉투라고 사용하지 말라고 하는데 그러면 널 객체는 봉투가 아닌가..? 고급봉투인거 아닌가... -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/cu.md: -------------------------------------------------------------------------------- 1 | # 1.2 생성자 하나를 주 생성자로 만드세요 2 | - 2 ~ 3개의 메서드와 5 ~ 10개의 생성자가 적당하다. 3 | - 하나의 주생성자와 다수의 부생성자 원칙 : 내부 속성은 오직 한 곳에서만 초기화해야 한다. 4 | - 생성자가 많아지면 유연성이 향상된다. (유지보수성이 향상, 중복코드 제거) 5 | - 메서드가 많아지면 클래스의 초점이 흐려지고 SRP를 위반할 수 있다. 6 | 7 | ```java 8 | class Cash { 9 | private int dolloars; 10 | Cash(int dollars) { 11 | this.dollars = dollars; 12 | } 13 | 14 | Cash(float dollars) { 15 | this((int) dollars); 16 | } 17 | } 18 | ``` 19 | 20 | - 주생성자는 마지막에 둔다는 부분은 솔직히 공감되지 않는다. 생성자는 클래스의 속성과 관련이 있으니 최상단에 두는 것이 정보가 집중되지 않을까? 생성자에서 메서드 호출부가 있다면, 가장 하단에 두어 메서드와 근접하게 두는 것이 이점이 있을 수 있지만, 생성자에 코드를 넣지말라는 다음 장의 메시지를 비추어 볼 때 납득이 가지 않는다. 21 | 22 | - 2 ~ 3개의 메서드는 Public Method만을 의미하는 것이겠지? 2.1 에서 하나의 객체는 4개 이하의 객체를 캡슐화하라고 되어 있는데, 2.4 절에서 이야기하는 빌더와 조정자를 포함해서 메서드 2 ~ 3개가 가능한가? 23 | 24 | -------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 2.7 문서를 작성하는 대신 테스트를 만드세요 2 | 3 | ## 정리 4 | - 이상적인 코드는 스스로를 설명하기 때문에 어떤 추가 문서도 필요하지 않다. 5 | - 코드를 문서화하는 대신 코드를 깔끔하게(clean) 만들어야 한다. 6 | 7 | ``` 8 | Employee jeff = department.employee("Jeff"); 9 | jeff.giveRaise(new Cash("$5,000")); 10 | if (jeff.performance < 3.5) { 11 | jeff.fire(); 12 | } 13 | ``` 14 |
15 | 16 | - 단위 테스트는 클래스의 일부이다. 독립적인 개체(entity)가 아니다. 17 | - 단위 테스트가 바로 문서화이다. 18 | - 하나의 단위 테스트는 한 페이지 분량의 문서 만큼이나 가치가 있다. 19 | - 단위 테스트를 올바르게 관리한다면, 실제 클래스보다 단위 테스트를 훨씬 더 자주 읽게 된다. 20 | - 단위 테스트가 올바르게 작성됐다면 실제 클래스를 이해하는데 큰 도움이 되고 국제적이기도 하다. 21 | - 따라서, 메인 코드만큼 단위 테스트에도 관심을 기울여야 한다. 22 | 23 | ## 느낀점 24 | 25 | - 우테코를 하면서 테스트 코드를 작성하는 방법을 배우고 익혔다. 26 | - 미션을 진행하며 테스트 코드의 장점을 몸소 느낄 수 있었다. 27 | - `단위 테스트가 바로 문서화`라고 하니까 앞으로도 테스트 코드를 더 잘 작성할 수 있도록 노력해야겠다! -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/joy.md: -------------------------------------------------------------------------------- 1 | # 2.8 모의 객체(Mock) 대신 페이크 객체(Fake)를 사용하세요 2 | ## 정리 3 | Mocking은 테스트를 장황하고 이해하기 어렵게 만든다. 4 | Fake를 사용하면 테스트를 짧게 만들 수 있다. 5 | 6 | Mocking은 객체의 내부 구현과 테스트를 결합시킨다. 객체의 행위(내부 구현)을 가정하고 그 가정을 하드코딩한다. 7 | 리팩터링해서 내부 구현이 바뀐다면 실제로는 완벽하게 동작하지만 테스트는 실패할 것이다. 하드코딩한 가정이 더 이상 유효하지 않기 때문이다. 이 것은 곧 단위테스트 신뢰를 무너뜨린다. 8 | 테스트가 객체 내부의 구현 세부사항을 알면 테스트가 취약해지고 유지보수하기 어려워진다! 9 | 10 | ## 느낀점 11 | 단위테스트의 퀄리티를 높이는 방법을 좀 더 구체적으로 생각해볼 수 있었다. 12 | 1. 클래스의 행동이 변경되면 테스트는 실패해야한다. 13 | 2. 클래스의 행동이 변경되지 않는다면 테스트는 실패하지 않아야한다. 14 | 3. 좋은 단위테스트는 리팩터링을 해도 살아남는다. 나쁜 단위테스트는 리팩터링후에 폐기된다. 15 | 16 | 체스 미션에서 리팩터링 후 전체의 절반정도의 단위테스트를 지우고 새로 작성한 경험이 있다. Mock 객체를 사용하진 않았지만 내부구현과 결합이 강하게 작성한 테스트들이었다. 17 | 요즘은 미션이 진행될수록 스프링공부에 집중한다는 핑계로 단위테스트 작성에 시간을 들이지 않고 있다. 18 | 체스 미션에서 지운 테스트들이 내가 단위테스트를 하기 싫게 만든 원인이 아닐까 반성했다. 19 | -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/gump.md: -------------------------------------------------------------------------------- 1 | # 2.1 -가능하면 적게 캡슐화 하세요 2 | 3 | ## **정리** 4 | 5 | ### **4개 이하의 객체를 캡슐화해요** 6 | 7 | 예외는 없다고 해요. 그 이상의 상태를 가진다면 구조를 잘못 짠 것이에요. 8 | 9 | ### **상태 없는 객체는 존재해서는 안되고, 상태는 객체의 식별자여야 해요** 10 | 11 | 내부에 캡슐화된 모든 객체들이 객체의 식별자를 구성하는 요소라는 사실에 동의한다면, 12 | 13 | x,y,z라는 좌표를 통해 모든 것을 구분할 수 있다는 것에 동의할 수 있다고 해요. 14 | 15 | > **어려워요.** 16 | 17 | 친구의 자동차가 제 자동차와 동일한 제조사, 모델, 제조년도의 자동차를 가지고 있더라도, 두 차는 여전히 다른차라는 말이에요. 즉, 자동차 객체는 타입과 차량 인식 번호( 차량 인식번호, VIN)을 캡슐화하고 타입은 다시 제조사, 모델, 제조년도를 캡슐화 하면 결과적으로 자동차 객체는 3개의 작은 객체를 포함하게 되요. 18 | 19 | ### **== 연산자를 사용하지 말고 항상 equals() 메서드를 오버라이드 해요** 20 | 21 | 자바의 결함(빈 객체도 서로 다르다고 하는(정의하지 않는 식별자를 가지게 하는) 결함)때문이에요. 22 | 23 | ## **의견** 24 | 25 | 객체를 어디까지 쪼갤 수 있냐라고 생각했는데, 정말 많이 쪼갤 수 있다고 생각했어요. 3개 이하, 많아도 4개의 객체를 가지게 한다는 점이 인상깊네요. 26 | 27 | x, y, z 축을 생각하면, 객체의 식별을 하기 위한 것들이 무엇일지 쉽게 생각해 볼 수 있어요. -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/nabom.md: -------------------------------------------------------------------------------- 1 | 메서드의 종류는 두가지로 나눌 수 있다. 빌더와 조정자. 2 | 3 | 빌더는 뭔가를 만들고 새로운 객체를 반환하는 메서드이다. 빌더의 반환타입은 절대 void가 될 수 없으며, 이름은 항상 명사여야 한다. 4 | 5 | ```java 6 | float speed(); 7 | Employee employee(int id); 8 | ``` 9 | 10 | 조정자는 엔티티를 수정하는 메서드이다. 조정자의 반환 타입은 항상 void이고, 이름은 항상 동사이다. 11 | 12 | ```java 13 | void save(String content); 14 | void remove(Employee employee); 15 | ``` 16 | 17 | ** Boolean 값을 결과로 반환하는 경우는 형용사로 표현한다. 18 | 19 | ```java 20 | boolean isEmpty(); X -> boolean empty(); 21 | boolean isNegative(); X -> boolean negative(); 22 | ``` 23 | 24 | ## 느낀점 25 | 26 | 확실히 메서드명이 명사인지 동사인지에 따라 리턴값의 유무를 짐작할 수 있어 좋은 것 같다. 27 | 28 | 하지만 많은 기존 라이브러리(대표적으로 `HttpMessageConverter`)가 getter를 이용해 동작하기 때문에 어쩔수 없이 getter를 사용해야한다. 이러한 상황에서 이런 라이브러리를 사용하지 않는다고 위와 같은 룰을 따르게 되면 한 프로젝트에 빌더의 반환타입에 대한 룰이 두가지가 생기게 된다. 그러면 개발자에게 더 혼선을 주지 않을까? -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 2.3 항상 인터페이스를 사용하세요 2 | 3 | 객체는 살아있는 유기체이다.
4 | 객체는 다른 유기체들과 서로 소통 하면서 그들의 작업을 지원하고 도움을 받는다.
5 | 즉, 객체들이 서로 필요로 하기 때문에 결합(coupled)되는 것이다.
6 | 하지만 애플리케이션이 커지기 시작하면서 객체 사이의 강한 결합도(tight coupling)이 생기고, 이는 유지 보수에 영향을 미친다.
7 | 8 |
9 | 10 | 따라서 최대한 객체를 분리(decouple)해야한다.
11 | 객체 분리란 상호작용하는 다른 객체를 수정하지 않고도 해당 객체를 수정할 수 있도록 만든다는 것이다.
12 | 그리고 이를 위해서 필요한 것이 '인터페이스(interface)'이다.
13 | 14 |
15 | 16 | 강한 결합을 막기 위해 클래스 안의 모든 퍼블릭 메서드가 인터페이스를 구현하도록 만들어야한다.
17 | 철학적 관점에서 클래스 존재 이유는 누군가 클래스의 서비스를 필요로 하기 때문인데, 서비스는 계약이자 인터페이스이기에 이것은 어딘가에 문서화되어있어야한다.
18 | 즉, 동일한 인터페이스를 구현하는 여러 클래스들이 존재하고 언제든 대체 가능하게 만들어야한다. (느슨한 결합도, loose coupling)
19 | 20 |
21 | 22 | # 느낀점 23 | 24 | 여러 미션을 진행하면서 인터페이스를 통해 결합도를 낮췄을 때의 이점을 체감했다.
25 | 인터페이스를 잘 활용하면 여러 형식의 패턴으로 유연하게 코드를 작성할 수 있었다.
26 | -------------------------------------------------------------------------------- /3장-취업/5절-절대-getter와-setter를-사용하지-마세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 3.5 절대 getter와 setter를 사용하지 마세요 2 | 3 | ## 내용 4 | 5 | * 클래스는 어떤 식으로든 멤버에게 접근하는 것을 허용하지 않는다. 객체에게 메서드를 호출하면 해당 메서드가 실제로 어떤 방식으로 동작하는 지도 알 수 없고, 캡슐화된 어떤 멤버가 이 작업에 개입하는 지도 알 수 없다. 이것이 캡슐화다. 6 | * 모든 프로그래밍 스타일의 핵심 목표는 가시성의 범위를 **축소해서** 사물을 **단순화시키는** 것이다. 이 모든 이유는 유지보수성 향상을 위해서이다. 7 | * getter와 setter는 캡슐화 원칙을 **위반하기**위해 설계되었다. 8 | * ``getDollars()``는 부적절하고, ``dollars()``는 적절하다. 전자는 데이터 중에 dollars를 찾은 후 반환하세요 라는 의미이며, 후자는 얼마나 많은 달러가 필요한가요? 라고 묻는 것이기 때문이다. 객체를 데이터의 저장소로 취급하지 않고, 객체를 존중한다는 뜻이다. ``dollars()``는 데이터를 노출하지 않지만, ``getDollars()``는 데이터를 노출한다. 메서드의 이름을 절대 이런 방식으로 지어서는 안된다. 9 | 10 |
11 | 12 | ## 내용 및 의문점 13 | 14 | 이번 장은 공감이 간다. 하지만, 마지막 메서드 네이밍에 관해서는 조금 더 고려해봐야 할 것 같다. 팀의 코드 컨벤션에 따를 것 같다. 여러 로직이 들어가서 객체가 가지고 있는 데이터를 그대로 반환하는 것이 아니라면 get을 뗄 것 같다. 하지만 데이터를 그대로 getter를 통해 반환하게 되는 경우에는 get을 그래도 붙일 것 같다. -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/suri.md: -------------------------------------------------------------------------------- 1 | # 2.3 항상 인터페이스를 사용하세요 2 | 3 | - 객체들은 서로를 필요로 하기 때문에 결합된다. (협력) 4 | - 객체지향 세계가 커지면 커질 수록 강한 결합도 문제 발생 5 | - 결합 된 다른 객체를 수정하지 않고도 해당 객체를 수정 할 수 있어야 한다. 6 | 7 | ## 인터페이스 라는 도구 8 | 9 | ```java 10 | interface Cash { 11 | Cash multiply(float factor) 12 | } 13 | ``` 14 | 15 | - Cash는 인터페이스 16 | - Cash는 다른 객체와 의사소통 하기 위해 따라야 하는 계약 17 | 18 | ```java 19 | class Employee { 20 | private Cash salary 21 | } 22 | ``` 23 | 24 | 25 | 26 | - Employee는 Cash의 multiply에 관심이 없다. 27 | - 느슨한 결합도 28 | - 결합은 되어있지만 구현이 대체 될 수 있다. 29 | 30 | 31 | 32 | # 느낀점 의문점 33 | 34 | - public 메소드를 가진 모든 클래스에 대해서 인터페이스를 만들어야 하는가 35 | - 존재하는 여러 타입(클래스)로 부터 추상화를 진행해야하는가 (인터페이스) 36 | - 아니면 추상화를 먼저(인터페이스) 해야 하는가. 37 | - 예전에 검프랑 추상화에 대해서 말을 했던 것 38 | - 사람들이 살아오면서 무언가를 두드리는 도구들을 추상화 하여 망치가 됨 39 | - 인터페이스를 먼저 정의 하기보다는 구현을 해보면서 추상화를 해야 겠다는 생각이 들 때 추상화를 진행해야 되지 않을까? -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/cu.md: -------------------------------------------------------------------------------- 1 | # 2.4 메서드 이름을 신중하게 선택하세요 2 | - 빌더의 이름은 명사로, 조장자의 이름은 동사로 짓는다. 3 | - Boolean 값을 반환하는 경우 빌더에 속하지만, 형용사로 짓는다. 4 | - 빌더란 새로운 객체를 반환하는 메서드를 의미한다. 5 | - 자율적인 객체로 존중한다면 할 일을 명령하기 보다는(프로시저), 무엇을 만들어야 하는지만 요청해야 한다. 6 | 7 | ```java 8 | float speed(); 9 | Employee employee(int id); 10 | String parsedCell(int x, int y); 11 | ``` 12 | 13 | - 조정자란 엔티티를 수정하는 메서드를 의미한다. 14 | 15 | ```java 16 | void save(String content); 17 | void put(String key, Float value); 18 | void remove(Employee employee); 19 | ``` 20 | 21 | 22 | 23 | ### 느낀 점 24 | 25 | - 클린코드는 메서드의 의도를 분명히 드러내라고 가이드하고 있다. 그래서 함수명을 동사로 작성하여 메서드가 1개의 기능만 담당하도록 한정짓고 있다. 빌더를 작성한다면 오히려 무엇을 만들어내기 위해 빌더가 여러 기능을 수행하더라도 확인할 방법이 없다. 왜냐하면 우리는 객체에게 그 방법을 일임했기 때문이다. 26 | - 우리는 여러가지 이유로 [불변객체](https://brainbackdoor.tistory.com/141)를 활용한다. 불변객체는 객체의 상태를 변경하는 메서드를 제공하지 않는다. 그럼 결국 빌더만 남게 되는 것일까?! 27 | -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 2.8 모의 객체 대신 페이크 객체를 사용하세요. 2 | 3 | 모킹은 나쁜 프랙티스이며, 최후의 수단으로만 사용해야한다. 4 | 모킹 대신 페이크 객체를 사용할 것을 제안한다. 5 | 6 | 페이크 객체는 interface의 일부이며 인터페이스와 함께 제공된다. 7 | ``` 8 | interface Exchange { 9 | float rate(String origin, String target); 10 | final class Fake implements Exchange { 11 | @Override 12 | float rate(String origin, String target) { 13 | return 1.2345; 14 | } 15 | } 16 | } 17 | ``` 18 | 페이크 클래스는 단위 테스트 안에서 Exchange 클래스를 쉽게 사용할 수 있도록 지원한다. 페이크 클래스는 실제보다 더 복잡해지는 경우도 있지만 모킹에 비해 다음과 같은 장점들을 제공한다. 19 | 1. 클래스 내부의 구현에 대해 알지 못해도 된다. <-> 모킹을 사용하면 가정 / 결과를 통해 구현하기 때문에 내부 구현을 알아야한다. 20 | 2. public 메서드를 변경했을 때 테스트에서 실패하지 않는다. 21 | 3. 인터페이스의 설계에 대해 더 깊이 고민하도록 해 준다. '테스트'라는 리소스를 사용해서 사용자와 동일한 기능을 구현한다. 22 | 23 | ## 의견 24 | 이 부분은 아직 이해가 잘 되지 않아 우선 내용만 올려두고 크루들의 의견 및 다시 읽어보는 과정을 통해 이해해볼 예정! -------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 정적 메소드를 사용하지 마세요 2 | 3 | # 느낀점 4 | - `객체 대 컴퓨터 사고` 에서 객체적 사고가 절차적 사고에 비해 유지보수성이 높으며 인지적으로 이해하기 쉽다는 내용은 이해한다. 그러나 정적 메서드의 존재 자체가 객체지향을 해친다고 하는 주장은 납득하기 어렵다. 정적 메서드는 도구에 불과하다. 저자가 한 것 처럼, 정적메서드 사용을 최대한 자제하며 객체지향의 극을 추구하는 것도 개발자의 영역이다. 유틸리티 클래스처럼 객체지향적이지 못한 패턴도 존재하지만, 중복을 제거하기 위한 퍼블릭 상수나 자주 반환하는 프로퍼티를 캐싱 해놓은 정적메소드가 객체지향을 좀먹는 절차지향의 악령인가? 흠'_' 5 | --- 6 | - 선언형스타일 대 명령형 스타일에선 성능 부분은 `생성자에 코드를 넣지 마세요` 가 다시 돌아온 느낌인데, 여전히 잘 모르겠다.. '_' 패스하고! 7 | - 다형성 활용은 인터페이스가 가진 최대 강점이므로 확장이 매우 용이하다는 본문 내용에 동의한다. 하지만 정적메소드는 활용성을 강점으로 가져가지 않겠는가?(아무 곳에서나 막 쓸 수 있으므로) 밸런스 밸런스 8 | - 표현력은 선언형이 명령형보다 낫다는 걸 인정한다. (그런데 사실 예고르가 작성하는 new 피라미드 쑈는 솔직히 잘 안 읽힌다) 응집도도 인정~! 9 | --- 10 | - 싱글톤은 생성시점을 제어할 수 있다는 것 말고는 유틸클래스와 비슷하다고 생각한다. 유틸 클래스가 객체지향에 악영향을 주는 것과 마찬가지 이유로 코드가 덜 객체지향스러워지는 데 에는 일조하겠지~? 그러나 필요한 상황에선 사용하자 11 | --- 12 | - `예고르는 무적이다. 객체지향은 신이고` 스타일로 살아가시는데 사는 게 즐거울 것 같다 13 | -------------------------------------------------------------------------------- /3장-취업/4절-충성스러우면서-불변이거나,-아니면-상수이거나/suri.md: -------------------------------------------------------------------------------- 1 | # 충성스러우면서 불변이거나, 아니면 상수이거나 2 | 3 | 4 | 5 | ## 상태와 데이터 6 | 7 | - 객체가 살아있는 동안 상태가 변하지 않는다. 8 | 9 | ```JAva 10 | class WebPage { 11 | private final URI uri; 12 | 13 | WebPage(URI path) { 14 | this.uri = path; 15 | } 16 | 17 | public STring content() { 18 | // HTTP GET 요청을 전송해서, 19 | // 웹 페이지의 컨테르를 읽은 후, 20 | // 읽혀진 컨텐츠를 UTF-8 문자열로 변환한다. 21 | } 22 | } 23 | ``` 24 | 25 | 26 | 27 | - content()를 실행 할 때마다 다른 값이 반환되더라도, 위 객체는 불변이다. 28 | - 객체를 대표하는 엔티티에 충성하기 때문이다. 29 | - 기본적으로 모든 객체는 식별자, 상태, 행동을 포함 30 | - 불변 객체와 가변 객체의 중요한 차이는 불변 객체에는 식별자가 존재하지 않으며 절대로 상태를 변경할 수 없다는 점 31 | - 불변객체의 식별자는 객체의 상태와 완전히 동일하다. 32 | - 동일한 URI를 가진 두 개의 WebPage 인스턴스는 content에서 항상 같은 값을 리턴한다. 33 | 34 | # 느낀점 35 | 36 | - 아직 잘 모르겠다.. 37 | 38 | - 아래 글을 한 번 더 읽어봤다. 39 | 40 | - https://woowacourse.github.io/javable/post/2020-05-18-immutable-object/ -------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 3.2 정적 메소드를 사용하지 마세요 2 | 3 | ## 정리 4 | - 정적 메소드는 명령형 스타일이다. 5 | - 알고리즘(algorithm)과 실행(execution)의 관점에서 사고하게 만든다. 👉 이 대신 객체(object)와 행동(behaviour)의 관점에서 사고해야 한다. 6 | - 정적 메소드를 절대 사용해서는 안된다. 7 | - 그러나, 이미 수많은 오픈소스 라이브러리에서는 이를 사용하고 있다. 👉 소스코드가 객체를 직접 처리할 수 있도록 정적 메소드를 감싸는 클래스를 만들어 이를 고립시켜 해결해야 한다. 8 | - 싱글톤 패턴 역시 피해야 한다. OOP에서 안티 패턴이다. 9 | - 정적 메소드와 싱글톤 패턴의 핵심적인 차이점은? 10 | - 정적 메소드는 분리가 불가능한 하드코딩된 결합도를 가진다. 11 | - 반면, 싱글톤 패턴은 분리 가능한 의존성(dependency)로 연결되어 있다. 12 | - OOP는 더 작은 객체들을 기반으로 더 큰 객체들을 조합하는 작업이다. 13 | - 따라서, 소프트웨어 어디에서도 `static` 키워드를 사용해서는 안된다. 14 | 15 | ## 느낀점 16 | 17 | - 명령형 프로그래밍과 선언형 프로그래밍의 차이점을 잘 이해하지 못하고 있었는데 3.2.2를 읽고 이해할 수 있었다. 18 | - 코드 실행 관점에서 두 개념을 비교한 게 좋았다. 19 | - 저자의 의견대로 소프트웨어를 개발하면 완벽히 객체지향적인 프로그램이 만들어질 것 같다. 20 | - 그런데, 이상과 현실은 다르니까 어느정도 유연하게 코드를 작성할 필요가 있다고 생각한다. 람다 표현식 사용할 수도 있지,, -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 불변 객체로 만드세요 2 | - 책 전체에서 가장 와닿았던 챕터! 불변 객체로 만들어라 3 | - 불변 객체의 장점은 다음과 같다 4 | 1. 식별자 가변성, 상태가 변함으로써 동일하던 객체가 달라지는 문제를 해결할 수 있다. 5 | 2. 실패 원자성, 완전하거나 실패하거나! 상태 변화가 중간에 실패함으로써 생길 수 있는 객체의 불완전성 문제가 해결된다. 6 | 3. 시간적 결합 제거, setter가 존재하면 객체 바깥에서 setter 호출 순서에 따라 시간적 결합 문제가 발생한다. 7 | 4. 부수효과 제거, 상태 변화가 줄 예기치 못한 버그를 방지할 수 있다. 8 | 5. NULL 참조 제거, NULL 참조는 가장 흔한 exception인 NPE를 발생시키고, 언제 할당되는지 알쏭달쏭하여 유지보수성이 저하된다. 불변객체는 NULL참조를 제거할 수 있다. 9 | 6. Thread-Safe, 여러 스레드에서 동시에 참조되어도 안전하다. 10 | 7. 객체가 더 단순해진다(?) 11 | 12 | # 느낀 점 13 | - 다른 항목들은 좋은 이유에 대해서 완-전히 공감한다. 어쩔 수 없는 경우를 제외하고는 불변으로 만드는 것이 좋다고 생각한다. 하지만 상태패턴을 활용할 때와, 이번에 DB를 위한 DTO를 만들 때 생성시에 ID값을 미리 알 수 없는 문제 때문에(DB를 찌르기전엔 auto-increment인 id를 알 수 없었다.) null참조를 하고 나중에 setter를 활용하였다. 이럴 땐 어떻게 불변으로 활용할 수 있을까? 고민해볼 점~ 14 | 15 | - 불변함으로서 객체가 단순해진다는 게 무슨 의미일까? 상태변화에 관한 로직이 없으므로 더 코드가 단순해진다는 의미가 맞나? 알듯 말듯 하다. 16 | -------------------------------------------------------------------------------- /2장-학습/2절-최소한_뭔가는_캡슐화하세요/joanne.md: -------------------------------------------------------------------------------- 1 | ## 2.2 최소한 뭔가는 캡슐화하세요. 2 | 앞선 2.1 에서 가능한 적게 캡슐화하라고 했다. 그렇다면 0개는 어떤가? 3 | 객체의 필드는 객체의 상태이자 식별자이다. 어떤 것도 캡슐화하지 않은 클래스의 모든 객체는 동일하다. 프로퍼티가 없는 클래스는 정적 메서드와 유사하다. 이 클래스는 아무런 상태와 식별자도 가지지 않고 오직 행동만을 포함한다. 정적 메서드가 존재하지 않고 인스턴스 생성과 실행을 엄격하게 분리하는 순수한 객체지향에서는 기술적으로 프로퍼티가 없는 클래스를 만들 수 없다. 4 | 5 | 객체가 '무'와 비슷한 어떤 것이 아니라면 무언가를 캡슐화해야한다. 자기 자신을 식별할 수 있도록 하기 위해 무언가를 캡슐화해야한다. 어떤 것도 캡슐화하지 않는 상태는 객체 자신이 세계 전체가 된다. 오직 하나의 세계만 존재할 수 있기 때문에 이 클래스는 오직 하나만 존재해야한다. 6 | 7 | ### 의견 8 | 아무것도 캡슐화하지 않는다는 것은 객체를 식별할 수 있는 것이 아무것도 없다는 뜻인 것 같다. 객체의 식별자로서 최소 1개 이상의 프로퍼티는 필요하기 때문이다. 9 | 하지만 궁금한 점은, 정적 메서드와 같이 프로퍼티가 없는 경우이다. 이 경우 우선 뒷 장(3.2)에서도 절대 정적 메서드를 사용하지 말라고 하긴 하지만, 아직까지 왜!? 사용하면 안되는지가 이해가 잘 안된다. 책 내용에서 `어떤 것도 캡슐화하지 않는 상태는 객체 자신이 세계 전체가 된다. 오직 하나의 세계만 존재할 수 있기 때문에 이 클래스는 오직 하나만 존재해야한다.`라고 이야기했는데, 정적 메서드의 생성자를 private으로 막아두고 static method만 외부에서 사용할 수 있도록 하면 오직 하나만 존재할 수 있도록 하는 것을 만족시킬 수 있는거 아닐까 ?? (잘 모르겠다. ㅠ) 10 | 11 | -------------------------------------------------------------------------------- /3장-취업/1절-5개-이하의-public-메서드만-노 출하세요/gump.md: -------------------------------------------------------------------------------- 1 | # 3.1-5개 이하의 public 메서드만 노출하세요 2 | 3 | ## 정리 4 | 5 | ### 가장 우아하고, 유지보수가 가능하고, 응집력이 높으면서, 테스트하기도 용이한 객체는 작은 객체에요 6 | 7 | ### 1개의 public 메서드와 20개의 private를 가진 클래스는 작다고 말할 수 있어요 8 | 9 | 클래스의 크기를 정하는 기준으로 **public 메서드(protected 메서드 포함)의 개수**를 사용하기를 권해요. public 메서드가 많을수록 클래스도 커져요. 클래스가 커질수록 유지보수성은 저하돼요. 10 | 11 | ### 가장 적절하다고 생각하는 public 메서드의 개수는 5개예요 12 | 13 | 클래스 안에 포함된 public 메서드가 5개보다 적다면 만족스로운 수준이에요. 5개보다 많다면 클래스를 리팩토링할 필요가 있다는 것이에요. 14 | 15 | ### 더 작은 클래스는 유지보수하기도 쉬워요. 그 이유는 작기 때문이에요. 16 | 17 | 작은 클래스는 코드량이 적고, 메소드의 수가 적고, 에러를 찾기 쉽고, 수정하기도 쉬워요. 각각의 메서드가 객체의 진입점이고 진입점의 수가 적다면 문제를 더 쉽게 고립시킬 수 있어요. 18 | 19 | ### 클래스가 작으면 메서드와 프로퍼티가 더 "가까이" 있을 수 있기 때문에 응집도가 높아져요. 간단히 말해서 각각의 메서드가 클래스의 모든 프로퍼터를 사용해요 20 | 21 | 이것이 응집도의 정의에요. 클래스가 작을 때 클래스의 모든 메서드가 모든 프로퍼터와 상호작용해야해요. 그러지 않으면 연관성이 없는 독립적인 부분들이하나의 클래스 안에 뭉쳐있는 것과 다를게 없어요. 22 | 23 | ### 결론이에요. 메서드를 5개 이하로 유지하세요. 24 | 25 | ## 의견 -------------------------------------------------------------------------------- /3장-취업/3절-인자의-값으로-NULL을-절대-허용하지-마세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 3.3 인자의 값으로 NULL을 절대 허용하지 마세요 2 | 3 | ## 내용 4 | 5 | * ``mask == null``은 mask 객체에게 이야기하는 대신, 이 객체를 피하고 무시하는 방법이다. ``mask.empty()``를 이용하거나 iterator를 사용해서 반복하는 것이 더 나은 방법이다. 6 | * 포인터가 없어지고 객체만 남은 java에 null이 존재해야할 이유는 없다. 존재하지 않는 인자 문제는 널 객체를 이용해서 해결해야한다. 7 | * null 확인 로직으로 코드를 오염시켜서는 안된다. 메서드 인자로 절대로 사용하지 말아라 예외는 없다. 절대. 8 | 9 |
10 | 11 | ## 생각 및 의문점 12 | 13 | ```java 14 | public Iterable find(Mask mask) { 15 | if(mask == null) { 16 | throw new IllegalArgumentException(...); 17 | } 18 | } 19 | ``` 20 | 21 | 위 코드는 메서드 인자의 값으로 null을 허용하지 않기로 했는데도 불구하고 클라이언트가 null을 전달할 경우 대응할 방법 중 첫 번째 방법이라면서 나온 코드다. 이렇게 null 체크를 할거면 null을 리턴 받지 말라고 했던 근거 중 하나가 위반되는 것 같은데 뭐지 싶다. 뭐 예고르가 개인적으로 선호하는 방법은 아예 null을 무시하는 방법이라고는 하니깐 그런갑다 해야겠다. 22 | 23 | 개인적으로 null 리턴을 지양하는 것에는 동의한다. 프리코스 때 부터 그렇게 들어와서 거부감 없이 받아들여지는 것 같다. 아무래도 null 체크에 대한 코드를 작성하다보면 메서드 10~15줄 제한이 깨져서 싫었기 때문인 것 같다. -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/joy.md: -------------------------------------------------------------------------------- 1 | # 1-1. -er로 끝나는 이름을 사용하지 마세요 2 | ## 내용 정리 3 | ### 클래스는 객체의 팩토리 4 | 클래스는 객체를 생성(인스턴스화)한다. 개념적으로 팩토리 패턴과 new 연산자는 동일하다. 클래스는 객체를 **만들고, 추적하고, 적절한 시점에 파괴**한다. 5 | 클래스는 객체의 **능동적인 관리자, 저장소, 웨어하우스**로 생각해야한다. 클래스를 객체의 템플릿으로 바라보면 안된다. 6 | 7 | ### 클래스에 이름울 붙일 때는 "무엇을 하는지"가 아닌 "무엇인지"를 생각해야한다 8 | 객체는 **캡슐화된 데이터의 대표자**이다. 객체를 (내부의 데이터를 다루기 위한)**절차의 집합**이나 (객체 내부와 외부를 이어주는)**연결장치**가 아니다. 스스로 결정을 내리고 행동할 수 있는 자립적인 엔티티이다. 9 | 클래스의 이름이 '-er'로 끝난다면 객체는 어떤 데이터를 다루는 절차들의 집합일 뿐이다. 10 | 11 | ## 생각 12 | - 지금까지 클래스를 객체의 템플릿으로 바라보았는데 팩토리와 개념이 거의 같다는 것을 알게 되었다. 저자가 생각하는 완벽한 객체지향 언어가 어떤걸까 궁금해졌다. 13 | - 자바뿐 아니라 다른 객체지향 언어의 예시를 통해 시야를 넓힐 수 있었다. 14 | - 의문이 생겼다. 저자는 User나 Computer처럼 예외는 있다고 했다. 오랜시간이 지나고 의미가 정착되어 더 이상 compute라는 행위가 아닌 Computer 자체를 가르키기 때문에 괜찮다고 한다. 하지만 난 우리가 Controller를 생각할 때도 `controll한다`라는 행위가 아닌 `요청을 받고 응답을 처리하는 MVC패턴의 구성요소 중 하나`라는 하나의 개념으로 바라본다고 생각한다. 때문에 이미 충분히 "무엇을 하는지"가 아닌 "무엇인지"를 중점으로 둔 네이밍이 아닐까한다. 15 | -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/nabom.md: -------------------------------------------------------------------------------- 1 | ## 정리 2 | 3 | 클래스란? 객체의 팩토리 4 | 5 | 객체는 연결장치가 아니라 대표자여야 한다. (스스로 결정을 내리고 행동할 수 있는 자립적인 엔티티) 6 | 7 | 클래스에 이름을 붙일 때는 무엇을 하는지가 아니라 무엇인지를 생각해야 한다. 8 | 9 | -er로 끝나는 이름을 사용할 경우 결국 클래스가 무엇을 하는지에 포커싱이 된다. 10 | 11 | ## 느낀점 12 | 13 | 첫 장부터 객체지향의 중요 요소인 '객체를 자립적인 엔티티로 존중한다'라는 생각이 잘 드러나있다. 14 | 15 | 이 장의 핵심인 '무엇을 하는지' 가 아니라 '무엇인지'를 생각해야 한다라는 말이 있지만 이 점은 백프로 찬성하기는 힘들다. 물론, '객체지향' 만을 바라봤을 경우 객체가 '무엇'인지를 포커싱하는 것이 옳지만 '객체지향'의 한계로 인해 'AOP(관점지향)'가 나와 '무엇을 하는지'에 대한 포커싱도 생겨났다. 16 | 17 | 이 책에서 '-er'로 끝나는 잘못된 이름을 가진 클래스의 예시를 보여준다. 'Manager', 'Controller', 'Validator' 등등. 대부분의 예시는 스프링에서 내부적으로 'AOP'를 사용하는 클래스이다. 즉, 태생적으로 어쩔 수 없이 '무엇을 하는지'에 포커싱이 될 수 밖에 없는 클래스인 것이다. (예시가 잘못되었다고 생각한다.) 18 | 19 | 우테코에서 프로그램을 짤 때 `Domain` 혹은 `Model` 패키지 내부는 객체지향적으로 짜게 된다. 이때는 '무엇인지'에 포커싱이 되는 것이 맞다고 생각하지만 이 도메인을 뷰와 연결해주거나 (컨트롤러) 뷰에 뿌릴(뷰) 때는 '무엇을 하는지'에 포커싱이 되어야 하는 것이 맞다고 생각한다! 즉, 도메인 혹은 모델 부분이 아니라면 -er로 끝나는 이름을 사용해도 괜찮지 않을까 생각이 든다! 20 | -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 1. -er로 끝나는 이름을 사용하지 마세요. 2 | 3 | 클래스는 객체의 능동적인 관리자이다. (클래스는 객체의 어머니이다.) 4 | 5 | 클래스 이름을 짓는 적절한 방법은 객체가 노출하고 있는 기능에 기반한 것이 아닌, 클래스가 무엇인지에 기반해야한다. 6 | 예를 들어, CashFormatter가 아닌 Cash라고 명명해야한다. 7 | 즉, 객체는 그의 `capability`로 특징지어져야한다. 8 | 9 | 여기서 -er로 끝나는 이름을 짓는다면, 그것은 바로 **기능에 기반하여 클래스를 명명했다**고 볼 수 있다. 10 | ex. Manager, Controller, Handler, Converter 11 | 이와 상반된 명명으로는 Target, Content, EncodedText 등이 있다. 12 | 13 | ## 내 의견 14 | 기능에 기반하여 클래스명을 자주 짓는다. Controller를 제외한다 하더라도 PointCalculator, ResultMapper등 클래스가 하는 기능에 초점을 맞춰 클래스명을 짓는 것 같다. 15 | 이렇게 짓는 이유는 클래스를 생성할 때 무엇인지에 기반한 것이 아니라 기능에 기반했기 때문이다. (이것부터가 잘못된 것인가..? 예고르 형님,,) 16 | 예를 들어, 포인트를 계산해주는 기능을 담당하는 클래스를 만들자! 라는 생각으로 PointCalculator를 만들고, PointCalculator 내부에는 포인트를 계산해주는 기능이 주로 담긴다. 17 | 만약 PointCalculator를 단순히 CalculatedPoint / Point 등으로 명명한다면 왠지 도메인인 것 같은 느낌이 들고... 그 객체의 기능이 모호하게 느껴진다. 18 | 이러한 클래스의 경우에도 CalculatedPoint / Point와 같이 명명하는 것이 맞을까? 19 | -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 2.4 메서드 이름을 신중하게 선택하세요 2 | 3 | ## 내용 4 | 5 | * 조정 후 값을 반환하거나, 뭔가를 만드는 동시에 조작하는 메서드가 있어서는 안된다. ex) ``int save(String content)`` 6 | * 메서드의 이름을 동사로 지을 때에는 객체에게 '무엇을 할 지'를 알려주어야 한다. 객체에게 무엇을 '만들라고' 요청하는 것은 협력자에 대한 존중이 결여되어 있을뿐만 아니라 예의에도 어긋나는 방식이다. 7 | * 객체가 뭔가를 조정해야 한다면 이름은 동사이고 반환값이 없다. 8 | * Boolean 값을 결과로 반환하는 경우에는 형용사로 이름을 지어야 한다. 해당 메서드 네임 앞에 is 를 붙었을 때에 읽히게끔 말이다. 9 | 10 |
11 | 12 | ## 생각 및 의문점 13 | 14 | 조정자가 무언가를 리턴하는 것이 잘못됐다는 것에 나도 동의한다. 프로그래밍 학습 초기 때에 스택에 대해 배우면서 pop으로 날리는 것은 알겠는데 대체 왜 리턴을 하는거지? 리턴하는게 대체 뭘까 라는 의문을 가진적이 있었다. 하지만, 조정을 하는 동시에 값을 리턴하는 것이 애플리케이션을 제작하는 입장에서 편하다는 것을 깨달았다. 수행과 조회를 나누면 조회를 한 번더 해줘야 되니깐?? 자바에서 제공하는 메서드들 중에서도 조정자임에도 불구하고 값을 반환하는 경우의 메서드가 있는데 그냥 받아들여야 되는 예외라고 생각하는게 편할 것 같다. 그리고 53쪽에 ``빌더 패턴은 ctor에 너무 많은 인자를 전달하고 싶지 않을 때 유용하게 사용할 수 있습니다. 하지만 애초에 인자의 수가 많다는 것 자체가 문제입니다.`` 라고 언급을 하는데, 빌더 패턴이니깐 오히려 인자의 수가 많다는 것에 대해 너그럽게 봐도 되지 않을까? 라는 생각을 하게된다. Boolean 타입일 경우 형용사여야 된다는 굉장히 납득이 간다!! -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## -er 로 끝나는 이름을 사용하지 마세요. 2 | 3 | 4 | 5 | - 클래스는 객체를 생성한다. 클래스가 객체를 instantiate 한다. 6 | 7 | - 자바에서 new 연산자로 할 수 있는 유일한 작업은 객체라고 불리는 클래스의 인스턴스를 생성하는 것 뿐 8 | 9 | - new 가 동작하는 방식을 변경할 수 있는 어떤 매개변수도 제공하지 않음 10 | 11 | - 개념적으로 팩토리 패턴과 new 연산자는 동일 12 | 13 | - 자바에서 팩토리 패턴은 new 연산자를 확장한 것처럼 동작, new 연산자가 실행되기 전에 부가적 로직을 추가할 수 있기 때문에, 보다 유연하고 강력하게 만들 수 있음. 14 | 15 | - 클래스를 객체의 능동적인 관리자로 생각해야 함 16 | 17 | 18 | 19 | 이름짓기 20 | 21 | - 무엇을 하는지 x 22 | 23 | - 무엇인지 o (무엇을 할 수 있는지?) 24 | 25 | 무엇을 하는지와 무엇인지의 차이는 이해가 되자만,,,, 무엇을 하는지와 무엇을 할 수 있는지 같은것은 별로 좋은 표현이 아닌듯. 26 | 27 | 28 | 29 | - 객체는 연결장치가 아닌 대표자여야 함 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 체스 미션에서 직렬화된 값을 역직렬화 후 객체를 생성하기 위해 38 | 39 | Utils 패키지에 Serializer라는 클래스를 생성, serialize, deserialize 와 같은 메서드를 넣음. 40 | 41 | 책에 따르면 이 방법은 잘못된 방법 42 | 43 | 생성자에서 처리해줘야 할듯 싶음. 44 | 45 | 안쓸라면 안쓸 수 있겠지만.. 음 씀으로써 불편함을 느껴보질 못해서 느껴봐야할듯... 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/suri.md: -------------------------------------------------------------------------------- 1 | # 2.1 가능하면 적게 캡슐화하세요 2 | 3 | 4 | 5 | - 복잡성은 직접적으로 유지보수성에 영향을 미침 6 | - 4개 또는 그이하의 객체를 캡슐화 할 것을 권장 7 | 8 | 9 | 10 | - 내부에 캡슐화된 객체 전체를 가리켜 상태 또는 식별자라고 부른다. 11 | 12 | 13 | 14 | - 저자가 이해하고 있는 OOP 15 | 16 | ``` 17 | '객체'는 고수준의 행동을 낳기 위해 함께 동작하는 객체들의 집합체 18 | ``` 19 | 20 | 21 | 22 | - 상태 없는 객체는 존재해서는 안되고, 상태는 객체의 식별자여야 합니다. 23 | 24 | 25 | 26 | - 제조사, 모델, 제조년도의 자동차를 가지고 있더라도 두 차는 여전히 다른차가 아니냐? 27 | - 실세계의 자동차가 객체지향 세계의 자동차 보다 훨씬 복잡 28 | - 자동차 관리 프로그램이라는 객체지향 세계에서는 저 3개의 식별자가 같다면 같은 객체로 볼 수 있다. 29 | - 실제 생활에서는? 자동차 타이어 브랜드, 기스, 등등등 식별자들이 매우 많음 30 | 31 | 32 | 33 | - Java 결함을 해결하기 위해 == 연산자를 사용하지 말고 항상 equals() 메서드를 오버라이드 하라 34 | 35 | 36 | 37 | # 느낀점 38 | 39 | 이번 체스 미션을 할 때 Piece가 상태를 갖지 않도록 구현하였다. 40 | 41 | 구현 할 때는 position, type, color와 같은 데이터를 굳이 piece가 들고 있지 않아도 될 것 같았다. 42 | 43 | 데이터를 주입받아서 행동을 하게 piece를 만들었다. 44 | 45 | piece를 자율적인 객체가 아니라 매우 수동적인 객체로 만들었다. 46 | 47 | 이번 장을 읽고, 내가 piece한테 큰 잘못을 했구나 하고 느꼈다. 48 | 49 | -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 2.3 항상 인터페이스를 사용하세요 2 | 3 | ## 정리 4 | - 객체들은 서로 필요로 하기 때문에 결합된다(coupled). 5 | - 객체 사이의 강한 결합도(tight coupling)은 유지보수성에 영향을 미친다. 6 | - 따라서, 객체를 분리하여(decouple) 어플리케이션 전체를 유지보수 가능하도록 만들어야 한다. 👉 이를 가능하게 만드는 게 인터페이스(interface) 7 |
8 | 9 | ``` 10 | interface Cash { 11 | Cash multiply(float factor); 12 | } 13 | 14 | class Employee { 15 | private Cash salary; 16 | } 17 | ``` 18 | 19 | - 인터페이스를 활용하면 실제 구현 < 계약에 의존한다. 20 | - ex. Employee 클래스는 Cash 인터페이스의 구현 방법에 아무런 관심이 없다. 21 | - ex. multiply() 메소드가 어떻게 동작하는 지도 관심이 없다. 22 | - 객체 사이의 느슨한 결합도(loose coupling)을 유지해야 한다. 23 | - 클래스 안의 모든 퍼블릭 메소드가 인터페이스를 구현하도록 만들어야 한다. 👉 모든 퍼블릭 메소드가 오버라이드돼야 한다. 24 | 25 | ## 느낀점 26 | 27 | - 인터페이스를 사용하면 OCP, DIP를 지킬 수 있기 때문에 이번 주제에 전반적으로 동의한다. 28 | - 그렇지만, 클래스 안의 모든 퍼블릭 메소드가 인터페이스를 구현하도록 만들어야 한다는 의견에는 약간 의문점이 든다. 29 | - 예고르 말대로 코드를 작성하면 `클래스 implement 수많은 인터페이스`처럼 될텐데 코드가 지저분해 보이지 않을까..? 30 | - 이렇게 코드를 작성해 본 적이 없어서 괜찮은 방식인지 현재는 잘 모르겠다. -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 메서드 이름은 신중하게 선택하라 2 | - 빌더 와 조정자를 분리하라는 이야기는 일리가 있다고 생각을 한다. 메소드 시그니처를 구상할 때 'return 타입이 납득이 될까..?'' 하는 고민을 간혹 해봤기 때문이다. 나야 내 코드니까 당연히 return 되는 boolean, int, String이 무엇을 의미하는지 알지만.. 내 코드를 읽는 사람도 그럴까?? 3 | 4 | - 그런 맥락에서 빌더와 조정자를 원칙적으로 철저하게 분리하면 내 코드를 받아 쓰는 사람(혹은 미래의 나)의 오해를 막고 가독성도 증진시킬 수 있다고 생각한다. 5 | 6 | - 하지만 String read(File file)처럼, 오해의 소지가 없는 시그니처의 경우 굳이 분리할 필요가 없다고 생각한다. File의 read의 결과가 무엇일까? 당연히 읽어온 내용 아닌가? (만약 내용이 아니라 다른게 반환된다면 설계자의 잘못이라 생각한다) 7 | 8 | - 만약 jdbcTemplate의 int update() 처럼, 반환타입을 추정할 수 없는 경우에는 VO 사용을 제안한다. affetedRow update() 식으로! 9 | 10 | - 이러면 메소드가 한 가지 일을 해야한다는 원칙에 위배하지 않느냐는 반론이 나올거 같다. 일단 나는 객체지향 생초보임을 밝히고 시작하겠다. 빌더와 조정자라는 개념을 적용하여 바라보면 String read(File file)은 두가지 일을 수행하게 되는 거겠지만, 내 입장에서 보면 해당 메소드는 ''읽어온다" 는 하나의 역할을 수행한다. 해당 내용을 빌더와 조정자로 분리한다고 해도, 읽어오는 행위에 대한 변화가 일어났을 때 1개의 메소드를 수정해야 하는 것은 변하지 않는다. 너무 과하게 나눈 느낌이랄까... ㅎ 11 | 12 | - 결론 : 빌더와 조정자의 개념은 객체지향을 튼튼하고 일관되게 만들어주지만 거기에 너무 목매지 않아도 될 것 같다는 입장으로 마무리~! 13 | -------------------------------------------------------------------------------- /3장-취업/4절-충성스러우면서-불변이거나,-아니면-상수이거나/bepoz.md: -------------------------------------------------------------------------------- 1 | # 3.4 충성스러우면서 불변이거나, 아니면 상수이거나 2 | 3 | ## 내용 4 | 5 | * 불변 객체라고 상수처럼 매번 동일한 데이터가 반환된다고 생각해서는 안된다. 상수처럼 동작하는 객체는 단지 불변성의 특별한 경우일 뿐이다. 6 | * WebPage는 결과가 변하기 때문에 상수는 아니지만, 객체가 대표하는 엔티티에 '충성하기' 때문에 불변 객체로 분류된다. 7 | * 불변 객체의 식별자는 객체의 상태와 완전히 동일하다. 8 | 9 |
10 | 11 | ## 생각 및 의문점 12 | 13 | > 객체 팩토리를 완벽하게 구현하고 싶다면, 이런 제약을 잘 이해하고 동일한 상태를 캡슐화하는 중복된 인스턴스를 생성하지 말아야 한다. 14 | 15 | 1. 이 부분은 중복되게 사용하게 될 경우에 캐싱을 사용해라~ 라고 받아들여도 되나요 ? 16 | 17 | > 완벽한 객체지향 세계에는 불변 객체만 존재하기 때문에 equals()와 hashCode()라는 두 메서드가 필요하지 않습니다. 18 | 19 | 2. 그렇다면 p.129 에서는 왜 오버라이딩 해준거죠?? 그리고 만약 두 메서드를 오버라이딩 해주지 않은 상태에서 동일한 상태를 가지는 인스턴스를 생성해주었을 때, 동등하다고 판단을 못할텐데요. 작가가 이렇게 말한 이유는 불변 객체가 가지고 있는 그 상태는 유일무이 하기 때문에 새롭게 인스턴스를 생성해줄 이유가 없고, 서로 다른 불변 객체끼리는 동등하지 않은게 당연하기 때문에 두 메서드가 필요하지 않다고 한것으로 받아들여도 될까요?? 이것 또한 캐싱을 해놓아야 혹시 모를 에러를 방지하는 일일까요? 20 | 21 | 리스트에 add를 해도 불변으로 본다는 점이 굉장히 흥미로웠다... 전체적인 내용이 머릿속에 바로 와닿지 않아서 계속 읽어봤던 것 같다. 개인적으로 이 사람이 칭하는 상수를 나는 불변이라고 본다. 내부 콜렉션 값 또한 구성요소가 변하면 안된다고 생각한다. -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/wannte.md: -------------------------------------------------------------------------------- 1 | # 생성자 하나를 주 생성자로 만드세요 2 | ## 내용 정리 3 | 4 | 그래도 납득갈만한 주제이다. 5 | 6 | 2\~3개의 메서드와 5\~10개의 생성자를 포함하는 것이 적당하다. (또 시작이다.) 7 | 8 | 생성자의 객수가 많아질 수록 유연성이 향상된다. 9 | 10 | 메서드가 많아지면 SRP를 위반하게 된다. 11 | 12 | 주 생성자는 가장 마지막에 → 유지보수성 향상 13 | 14 | 유효성 검사와 같은 로직을 주생성자에서만 진행하여 코드 중복 방지 → 유지보수성 향상 15 | 16 | ## 읽고 느낀 점 17 | 18 | 다양한 생성자를 만들고 이를 하나의 주생성자로 관리한다는 생각에 동의한다. 19 | 20 | 5~10개의 생성자는 큰 의미를 지니는 것이라 생각되지는 않는다. 사용될만한 생성자를 미리 만들어주는 정도가 적당하다고 생각한다. 21 | 22 | ## 의문점 (글 내용과는 조금 동 떨어진,, 그냥 궁금했던 거) 23 | 24 | 다음과 같은 상황에서는 어떤 식으로 구현을 하면 좋을까요? 25 | 26 | Cash 객체는 항상 양수 만을 입력을 받습니다. 27 | 28 | Cash 끼리의 minus 기능을 추가하려고 합니다 29 | 30 | ```java 31 | public Cash minus(Cash other) { 32 | ~~~~ 33 | } 34 | ``` 35 | 36 | 만약 other의 값이 더 큰 경우에는 음수인 Cash를 리턴하게 되서 error 가 생기게 되는데요. 37 | 38 | 그냥 int, long, double로 리턴받는 거는 Cash처럼 못써서 별로.. 39 | 40 | Cash위에 또다른 클래스 CashMother 를 두고 Cash가 이를 상속 받게 하고, minus가 CashMother를 리턴하도록 수정을 해야할까요.. 41 | 42 | 아니면 Cash를 입력받는 로직한테 양수의 책임을 넘겨줘야 할까요. 43 | -------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 3.2 정적 메서드를 사용하지 마세요. 2 | 3 | 저자가 말하는 정적 메서드를 사용하면 안되는 이유는 다음과 같다. 4 | 5 | 절차적인 프로그래밍과 다를 바 없다. 우리는 객체 내부에서 어떻게 구현되어있는지 알 필요 없이 단순히 `정의`만 하면 된다. 6 | ``` 7 | Number max = new Max(2); // O 8 | Number max = Max.max(2); // X 9 | ``` 10 | 11 | 객체를 통한 lazy-loading이 불가능하다. 값이 필요하지 않은 시점에 CPU가 그 값을 알고 있을 필요가 없다. 호출하는 횟수가 증가하면 lazy-loading을 이용한 방법이 훨씬 빠르다. 12 | 13 | 다형성이 불가하다. (불가하다고 하니깐 말이 이상하긴 하지만..) 객체 내부의 메서드들로 이루어져있기 때문에 다른 객체로 대체하기 위해서는 새로운 정적 메서드를 생성해야한다. 14 | 15 | 객체를 통한 조합이 불가능하다. 진정한 OOP는 작은 객체를 더 큰 객체로 만들어가는 과정이다. 16 | 17 | ## 의견 18 | 19 | 정적 메서드를 이용한 유틸 클래스가 있다고 가정한다면, 그 클래스의 메서드는 어디서든 사용할 수 있다는 장점이 있다. 하지만 사용되지 않을 경우 불필요하게 메모리에 로드된다는 단점이 있다. 그러나 정적 팩토리 메서드 같은 경우에는 정적 팩토리 메서드를 이용함으로서 객체의 생성을 객체 내부에서만 수행할 수 있도록 하고, 하나의 인스턴스만을 만들 수 있도록 보장할 수도 있다. 또한 앞서 생성자 내부에 메서드를 만들지 말라고 했었는데, 생성자 내부에서 수행할 수 있는 로직인 검증 같은 것들을 정적 팩토리 메서드에서 수행할 수도 있다. 아무튼..... 정적 팩토리 메서드는 필요한데, 불필요한 유틸 클래스는 사용하지 않는 것이 낫다고 생각한다. 유틸 클래스에서 수행하는 책임? 기능?을 알맞은 도메인에게 책임을 수행하도록 넘기는 것이 좋은 방법이라고 생각한다. -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/joanne.md: -------------------------------------------------------------------------------- 1 | ## 1.3 생성자에 코드를 넣지 마세요. 2 | 3 | 생성자에서 객체를 초기화할 때에는 코드가 없어야하고 인자를 건드려서는 안된다. 대신, 필요하다면 인자를 다른 타입의 객체로 감싸거나 가공하지 않은 형식으로 캡슐화해야한다. 예를 들어 다음과 같은 것은 잘못된 방법이다. 4 | ``` 5 | class Cash { 6 | private int dollars; 7 | Cash(String dlr) { 8 | this.dollars = Integer.parseInt(dlr); 9 | } 10 | } 11 | ``` 12 | 13 | 위와 같은 방법 대신, 우리는 다른 타입의 객체로 감싸 할당할 수 있다. 즉, 생성자에서는 단지 객체의 상태를 초기화만 해야하지 별도의 로직을 수행하지 않아야한다. 이는 작은 객체의 조합으로 더 큰 객체를 만들어간다는 점에서 진정한 객체지향에서의 인스턴스화를 이루어낼 수 있으며, lazy-loading을 통해 실제 필요한 시점에서 파싱될 수 있도록 파싱되는 시점을 늦출 수 있다는 장점을 갖는다. 14 | 15 | 따라서 생성자에서 코드를 없애면 사용자가 쉽게 제어할 수 있는 투명한 객체를 만들 수 있다. 객체는 **요청받을 때만 일하고, 그 전에는 어떠한 일도 하지 않게 된다.** 16 | 17 | ### 의견 18 | 19 | 생성자에 코드를 넣지 않는다면, 입력값이 null인 경우엔 어떻게 해야할까? 20 | lazy-loading을 수행한다면 그 값이 필요한 시점에서야 null임을 확인하고 예외를 발생시켜야하는 걸까? 21 | 22 | 난 아직까지 생성자에서 검증 로직을 수행해도 된다고 보지만, 꼭 1.3을 지켜야한다면 미리 검증을 외부에서 수행하고 할당하는 방법이 있을 것이다. 즉, 클래스의 생성자에서는 파라미터로 들어온 값이 **이미 검증된 값**일 거라고 믿고 초기화 로직만 수행하도록 하고, 유효성 검증은 입력받는 곳 (InputView?)에서 수행할 수 있을 것 같다. 🧐 -------------------------------------------------------------------------------- /3장-취업/7절-인트로스펙션과-캐스팅을-피하세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 3.7 인트로스펙션과 캐스팅을 피하세요. 2 | 3 | What is Introspection? 4 | 빈의 프로퍼티와 메소드 등을 알려주기 위해 빈의 디자인 패턴을 자동으로 분석하는 과정 5 | 6 | 객체의 클래스, 구현 메소드, 필드 등의 객체 정보를 조사하는 과정을 의미한다. 7 |

8 | - 참고 : http://eclian.sys4u.co.kr/wp-content/uploads/2012/12/Java-Reflection-Introspection_SYS4U-IC.pdf 9 |

10 | What is TypeCasting? 11 | 타입캐스팅이란, 형변환을 말한다. 12 |

13 | 모두 **런타임에 객체의 클래스를 확인하는 데 사용된다**. 14 | 하지만 이는 모두 안티패턴이므로 사용하면 안된다. 15 | 방문한 객체에 대한 기대를 문서에 명시적으로 기록하지 않은 채 외부에 노출시킨 것과 같기 때문이다. 16 | 또한 런타임에 객체의 상태를 확인하므로 소스코드를 확인해야만 그 객체가 어떤 타입으로 형변환되었는지 파악할 수 있어 유지보수하는 것이 매우 어렵다. 17 | 18 | ## 의견 19 | 이전 상태 패턴을 쓸 때 현재 상태가 Blackjack인지, Hit인지 확인하기 위해 if (state instanceof Blackjack) 과 같은 코드를 작성한 적이 있다. 이런 경우에는 런타임에 객체를 비교할 수 있어 좋았으나 어떻게 작성하느냐에 따라 다른 결과를 낼 때도 있었다. 따라서 OOP도 만족시킬 수 있도록 상태에게 너 지금 어떤 상태이니?라고 물어보는 코드로 바꿨던 기억이 난다. 20 | 아무튼, 난 타입캐스팅이나 형변환은 사용하지 말자!에 한표이지만, 상속 관계에 있을 때 upcasting은 사용해도 되지 않을까...? 21 |

22 | - 참고 : https://madplay.github.io/post/java-upcasting-and-downcasting 23 | -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/koda.md: -------------------------------------------------------------------------------- 1 | # 내용 2 | 3 | - 1) 응집도가 높고 2) 견고한 클래스를 설계하면 2 ~ 3개의 public 메서드와 5 ~ 10개의 생성자를 포함한다. 4 | 5 | 여러가지 생성자를 통해서 유연한 클래스를 설계하고 클래스를 더 잘 활용할 수 있도록 한다. 6 | 7 | 여러 생성자를 활용할 때 다음을 지켜라. 8 | 9 | - 다른 부 생성자들이 단 하나의 주 생성자를 부르도록 한다. 10 | - 번외로 작가는 주 생성자를 모든 부 생성자 뒤에 두는 컨벤션을 지킨다. 11 | 12 | 위 내용의 이점으로는 13 | 14 | - 중복 코드를 방지 15 | - 예를 들어서 어떤 특징을 보장하기 위해서 유효성 검사 로직을 한 군데만 추가하면 된다. (p.27) 16 | - 설계를 더 간결하게 함 17 | - 따라서 유지보수성이 향상됨 18 | 19 | # 의견 20 | 21 | - 여러 생성자를 두는 것이 유연하고 좋다는 것을 강의 중 듣고 다음 미션에 다양한 생성자를 추가했는데 확실히 클래스를 활용할 수 있는 유연함이 생겨서 편리했다. 22 | - 생성자 관련 코드가 길어지면서 클래스가 다소 지저분해진다고 느꼈었는데, public 메소드를 클래스 분리로 리팩토링하고 하나의 주 생성자로 귀결되도록 구현하면 더 깔끔해질 것이라고 생각한다. 23 | - 그래도 메소드 2-3개는 너무 적은 숫자가 아닌가 싶다.. 24 | - 실제로 체스게임 구현 때 Board의 코드가 너무 길어서 리뷰어에게 물어봤을 때 복잡한 프로그램에서 이정도 길이는 괜찮다고 조언해주셨다. 25 | - 어느정도 유연하게 대처해야 하는 부분인 것 같다. 26 | 27 | # 의문 28 | 29 | 1. 작가가 말한 응집도와 견고한 클래스는 구체적으로 어떤 의미인지 잘 모르겠다. 30 | 2. 중복 코드를 방지할 때, 단 하나의 주 생성자에 로직을 추가하여 방지할 수 있다고 이해 했는데 이후 1.3에서 주 생성자에 어떠한 로직도 추가하지 말라고 한 것과 어떻게 연결이 되는 걸까? 31 | -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/suri.md: -------------------------------------------------------------------------------- 1 | # 불변 객체로 만드세요 2 | 3 | - 불변객체 4 | - 인스턴스를 생성한 후에 상태를 변경할 수 없는 객체 5 | 6 | ```java 7 | Cash five = new Cash(5); 8 | five.mul(10); 9 | ``` 10 | 11 | 12 | 13 | - 식별자 가변성 14 | - 매우 혼란스러운 map이 된다. 15 | 16 | ```java 17 | Map map = new HashMap<>(); 18 | Cash five = new Cash("5"); 19 | Cash ten = new Cash("10"); 20 | 21 | map.put(five, "five"); 22 | map.put(ten, "ten"); 23 | 24 | five.mul(2); 25 | ``` 26 | 27 | 28 | 29 | - 실패 원자성 30 | - 완전하고 견고한 상태의 객체를 가지거나 아니면 실패하거나 둘 중 하나만 가능 31 | - 시간적 결합 32 | - 가변 객체의 수가 많은 상황에서 가변 객체들을 처리하는 연산들의 순서를 일일이 기억해야 한다면 유지보수에 있어 어려움이 얼마나 클까? 33 | - 부수효과 제거 34 | - 누구도 객체를 수정할 수 없다. 35 | - 객체의 상태가 변하지 않았다고 확신가능 36 | - NULL 참조 없애기 37 | - 언제 객체가 유효한 상태이고, 언제 객체가 아닌 다른 형태로 바뀌는 지를 이해하기 어렵다. 38 | - 스레드 안정성 39 | - 불변 객체는 실행 시점에 상태를 수정할 수 없게 금지한다. 40 | - 단순성 41 | - 불변객체는 생성자 안에서만 데이터 초기화가 가능하기 때문에, 불변 객체를 아주 크게 만드는 일이 불가능 42 | 43 | 44 | 45 | # 느낀점 46 | 47 | 우테코에 들어오기 전에는 final이라는 키워드를 상수에만 사용했었다. 48 | 49 | 상태가 바뀔 일이 없이 보장하는 불변객체.. 프로그래밍을 하면서 50 | 51 | 개발자에게 안정감을 주는 키워드가 아닌가 싶다. -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 1.2 생성자 하나를 주 생성자로 만드세요 2 | 3 | ## 정리 4 | - Java는 메소드 오버로딩(method overloading)을 지원해서 여러 생성자를 만들 수 있다. 5 | - 클래스의 응집도(cohesion)가 높고, 클래스가 견고할수록 적은 수의 메소드와 많은 수의 생성자가 존재한다. 6 | - 한 클래스에는 2-3개의 메소드와 5-10개의 생성자를 포함하는 게 적당하다. 7 | - 단일 책임 원칙(single responsibility principle)을 지킬 가능성도 높아진다. 8 | - 클래스에는 한 개의 주(primary) 생성자와 여러 개의 부(secondary) 생성자를 만드는 게 좋다. 9 | - 클래스의 유연성을 높일 수 있다. 10 | - 코드의 복잡성을 줄이고 중복을 제거할 수 있다. 11 | ``` 12 | class Cash { 13 | private int dollars; 14 | 15 | Cash(float dlr) { 16 | this((int) dlr); 17 | } 18 | 19 | Cash(String dlr) { 20 | this(Cash.parse(dlr)); 21 | } 22 | 23 | Cash(int dlr) { 24 | this.dollars = dlr; 25 | } 26 | } 27 | ``` 28 | - 결국, 핵심은 유지보수성이다. 29 | 30 | ## 느낀점 31 | - 그동안 미션을 하면서 한 개의 주 생성자와 여러 개의 부 생성자를 만드는 것에 대한 이점을 얻을 수 있었다. 32 | - 확실히 코드가 간결해지고 중복을 줄일 수 있었다. 33 | - 그래서 이 내용은 전반적으로 동의한다. 34 | - 그렇지만, 한 클래스에는 2-3개의 메소드와 5-10개의 생성자를 포함하는 게 적당하다는 부분은 아직 잘 모르겠다. 35 | - 해당 문장 바로 뒤의 '이 값을 뒷받침할 만한 과학적인 근거는 없으며, 임의로 정했을 뿐입니다.' 문장 때문에 설득당하지 못한 것 같다. 36 | -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 1-1. -er로 끝나는 이름을 사용하지 마세요 2 | 3 | ### 내용 4 | 5 | * 클래스는 객체의 팩토리이며 new 연산자는 팩토리 패턴과 동일하다. (팩토리 패턴은 new 연산자를 보다 유연하고 강력하게 만들 수 있다) 6 | * 클래스의 이름은 무엇을 하는지(what he does)가 아니라 무엇인지(what he is)에 기반해야 한다. 7 | * 클래스의 객체들이 무엇을 캡슐화할 것인지를 관찰하고 이 요소들에 붙일 적합한 이름을 찾아야 한다. 8 | 9 |
10 | 11 | ### 생각 및 의문점 12 | 13 | 객체의 이름을 지을 때에 해당 객체의 행동이 아닌 본질을 보고 이름을 지어야 한다라는 주장으로 이해했다. 예시로 CashFormatter 와 Cash를 예로 들었다. 그러나, 만약 Cash 객체가 오직 format 과 관련된 행동만 한다고 했을 때에 나는 과연 Cash라고 짓는 것이 맞는지에 대한 의문이 든다. 14 | 15 | what he does가 아닌 what he is에 기반해서 이름을 붙이라고 하였다. 그러나 재밌게도 what he does가 곧 what he is 인 경우가 종종 있다. 은행에 가면 지폐를 투입하면 지폐가 몇 장인지 세주는 기계를 쉽게 볼 수 있다. 이를 영어로 Bill Counter라고 부른다. 물론 실제로 수행하는 일도 bill을 count하는 일이다. 과연, -er이라고 무조건적으로 지양해야 한다는 말이 정말 옳은 것일까?? 16 | 17 | 내가 든 예시가 아닌 다른 예시에서 -er을 대체할 만한 이름이 있다고 가정하자. 물론 이 객체가 하는 일은 -er이 붙을법한 일을 한다고 가정한다. 이 경우에 대체가능한 이름으로 변경할 수도 있을 것이다. 그러나, 해당 이름으로 변경하는 것 또한 고민을 해봐야한다고 나는 생각한다. 사용자가 객체의 이름을 보고 어떤 역할을 하는지 단번에 알아볼 수 있는지. 즉, 직관적인 이름인지에 대한 생각을 해봐야 한다고 본다. 물론 작가의 주장에 납득하여 객체 지향에 푹 빠진채로 -er 안돼!! 할 수도 있겠지만 그 결정으로 인해 조금씩 쌓일 다른 개발자들의 낭비되는 시간을 고려해보는 것이 좋지 않을까? -------------------------------------------------------------------------------- /3장-취업/3절-인자의-값으로-NULL을-절대-허용하지-마세요/suri.md: -------------------------------------------------------------------------------- 1 | # 인자의 값으로 NULL을 절대 허용하지 마세요. 2 | 3 | 4 | 5 | - Null을 사용하는 것은 객체에게 이야기 하는 대신 이 객체를 피하고 무시하는 것 6 | - 객체를 존중한다면 7 | 8 | ```Java 9 | if (mask.empty()) { 10 | // 모든 파일을 찾는다. 11 | } else { 12 | // 마스크를 사용해서 파일을 찾는다. 13 | } 14 | ``` 15 | 16 | 17 | 18 | 더 개선한 코드 19 | 20 | ```Java 21 | public Iterable find(Mask mask) { 22 | Collection files = new LinkedList<>(); 23 | for (File file: allFiles) { 24 | if (mask.matches(file)) { 25 | files.add(file); 26 | } 27 | } 28 | return files; 29 | } 30 | ``` 31 | 32 | 33 | 34 | - mask 객체를 존중했다면 조건의 존재 여부를 스스로 결정하게 했을 것 35 | 36 | 37 | 38 | - Null대신 39 | 40 | ```Java 41 | interface Mask { 42 | boolean matches(File file) 43 | } 44 | ``` 45 | 46 | 47 | 48 | ```Java 49 | Class AnyFile implements Mask { 50 | @Override 51 | boolean matches(File file) { 52 | return true; 53 | } 54 | } 55 | ``` 56 | 57 | 58 | 59 | - 중요하지 않은 NULL 확인 로직으로 코드를 오염시켜서는 안됩니다. 60 | 61 | 62 | 63 | # 느낀점 64 | 65 | - Null을 다루는 방법 중 하나인 Optional이 나왔지만 이것을 이용해도 Null을 체크하는 것 만큼이나 부수적인 코드들이 많이 생기는 것 같다. 66 | - Null에 대해 조금 더 고민해 봐야겠다. 67 | 68 | -------------------------------------------------------------------------------- /2장-학습/1절-가능하면_적게_캡슐화하세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 2.1 가능하면 적게 캡슐화하세요 2 | 3 | ## 정리 4 | - 한 클래스는 최대 4개의 객체를 캡슐화해야 한다. 5 | - 이를 초과하면 해당 클래스는 완전히 잘못된 방식으로 구현된 것이다. 6 | - 클래스 내부에 캡슐화된 객체 전체를 가리켜 객체의 상태(state) 또는 식별자(identity)라고 부른다. 7 |
8 | 9 | ``` 10 | class Cash { 11 | private Integer digits; 12 | private Integer cents; 13 | private String currency; 14 | } 15 | ``` 16 | - Cash 클래스는 3개의 객체를 캡슐화하고 있다. 17 | - 이 3개의 객체들이 함께 모여 Cash 클래스의 객체를 식별한다. 18 | - 동일한 값의 달러, 센트, 통화를 캡슐화하는 Cash 클래스의 두 객체는 서로 동일하다(same). 19 |
20 | 21 | ``` 22 | Cash x = new Cash(29, 95, "Cash"); 23 | Cash y = new Cash(29, 95, "Cash"); 24 | assert x.equals(y); 25 | assert x == y; 26 | ``` 27 | - 객체 패러다임에서는 두 객체 x, y가 동일하다고 판단하는 반면, Java에서는 동일하지 않다고 판단한다. 28 | - 이는 Java 언어가 안고 있는 설계적 결함 때문이다. 29 | - 두 객체 x, y의 상태는 동일하지만 식별자는 서로 다르다. 30 | - Java 언어의 결함을 해결하기 위해 항상 `equals()` 메소드를 오버라이드해야 한다. 31 | - Lombok 프로젝트에서 제공하는 `@EqualsAndHashCode` 어노테이션을 사용해도 된다. 32 | 33 | ## 느낀점 34 | 35 | - 클래스를 작게 유지해야 한다는 저자의 의견에 동의한다. 36 | - 체스 미션에서는 이를 제대로 못 지킨 것 같다. 시간이 되면 리팩토링하면서 고쳐봐야지. 37 | - `equals()`, `hashCode()` 메소드 대신 `@EqualsAndHashCode` 어노테이션을 사용할 수 있다는 걸 처음 알았다. 다음에 한번 써봐야겠다! -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/suri.md: -------------------------------------------------------------------------------- 1 | # 1.3 생성자에 코드를 넣지 마세요. 2 | 3 | - 객체 초기화에는 코드가 없어야 하고 인자를 건드려서는 안됩니다. 4 | 5 | - 대신, 필요하다면 인자들을 다른 타입의 객체로 감싸거나 가공하지 않은 형식으로 캡슐화 해야합니다. 6 | 7 | 8 | 9 | - 진정한 객체지향에서 인스턴스화란 더 작은 객체들을 조합해서 더 큰 객체를 만드는 것을 의미합니다. 10 | 11 | - 객체들을 조합해야 하는 단 하나의 이유는 새로운 계약을 준수하는 새로운 엔티티가 필요하기 때문입니다. 12 | 13 | 14 | 15 | - "5"라는 텍스트 객체 대신 new Cahs("5")를 사용하는 이유 16 | - "5"가 필요로 하는 계약을 따르지 않음 17 | - 이것이 다른 타입의 새로운 객체를 생성해야 했던 이유 18 | 19 | 20 | 21 | - 기술적인 이유 22 | - 생성자에 코드가 없을 경우 성능 최적화가 더 쉬워져서 실행 속도가 빨라짐 23 | - 생성자에 들어 간 로직은 생성시 무조건 실행되기 때문에 제어가 어려움 24 | 25 | 26 | 27 | - lazy 28 | - 객체는 요청 받을 때만 행동하고 그 전에는 어떤 일도 하지 않습니다. 29 | 30 | 31 | 32 | - 일관성 33 | - 우리는 이 클래스의 미래에 어떤 일이 일어날지 모른다. 34 | 35 | # 느낀점 36 | 37 | 계약이라는 말이 자주 등장해서 찾아봤는데 좋은 객체의 7가지 덕목에 "객체는 계약에 따라 동작한다."가 있다. 38 | 39 | https://codingnuri.com/seven-virtues-of-good-object/ 40 | 41 | 이걸 토대로 1.3장을 조금 더 생각해보았다. 42 | 43 | - 돈과 관련 된 계약을 수행 할 Cash 타입이 필요 44 | - String "5" 대신 Cash가 필요한 이유 45 | - 계약은 인터페이스와 같이 객체의 행동을 명세 -> 객체들은 계약 한대로 움직이길 예상함 46 | - 생성자에 로직이 들어가 버리면??? -> 예상불가 47 | - 생성자는 인터페이스에 정의되지 않음 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 2.3 항상 인터페이스를 사용하세요. 2 | 3 | 객체 간의 관계를 형성하기 위해서는 결합이 필요하다. 하지만 수 십개의 객체가 관계를 형성하기 위해 결합을 맺는다면, 강한 결합도가 문제가 된다. 따라서 우리는 객체 간의 관계를 맞을 수 있도록 하며, 객체를 분리하기 위해 인터페이스를 사용할 수 있다. 인터페이스는 다른 객체와 소통하기 위한 계약이라고 볼 수 있다. 특정 인터페이스를 구현하는 것은, 그 인터페이스에 담긴 계약을 준수한다고 보면 된다. 더불어 클래스 안의 모든 퍼블릭 메서드가 인터페이스를 구현하도록 만들어야한다. 올바르게 설계된 클래스라면 최소한 하나의 인터페이스라도 구현하지 않는 퍼블릭 메서드를 포함시켜서는 안된다. 4 | 5 | ## 의견 6 | Q: 인터페이스를 통해 객체간의 관계를 맺는 것은 이해가 되는데, 클래스 안의 모든 퍼블릭 메서드가 인터페이스를 구현하도록 만들어야한다는 점은 이해가 잘 되지 않는다. 인터페이스를 구현한 클래스의 퍼블릭 메서드를 말하는 것일까? 인터페이스를 구현한 클래스의 퍼블릭 메서드는 인터페이스의 계약을 구현하는 것으로만 이루어져야한다는 뜻일까? 7 | 8 | 내가 생각한 A: 이 책에서는 인터페이스를 통한 관계가 중요하다고 이야기하고 있기 때문에, 클래스 내부의 모든 퍼블릭 메서드가 인터페이스의 계약을 오버라이드 한 메서드여야 다른 객체와 관계를 맺을 때 상위 인터페이스로서 계약을 맺을 수 있다는 것을 말하고자 하는 것 같다. 즉, 객체 내부의 퍼블릭 메서드에 결합되는 것이 아닌, 그 객체의 퍼블릭 메서드가 오버라이드 한 상위 인터페이스와 관계를 맺도록 하라는 의미인 듯 하다. 9 | 10 | 인터페이스를 통해 객체간의 관계를 맺도록 하려면 징검다리인 Abstract Class나, 인터페이스 내부 구현 클래스 (?) 책 어딘가에서 봤는데 자세히 기억이 안난다. 가 꼭 필요한 것 같다. 왜냐면 이번에 Chess Piece를 Interface <-> 각 말들 이런 식으로 인터페이스를 통해 관계를 맺도록 했는데, 그렇게 되니 Interface 내부 중복이 너무 많이 발생하고 특정 클래스에는 불필요한 계약을 구현해야하는 경우도 있었다. 이후에는 Abstract Class를 내부에 둠으로서 해결하긴 했다. 쓰다 보니 이 말을 왜 쓴지 기억이 안나는데 다시 읽어보고 추가할 게 있으면 PR 코멘트로 남겨야겠다.. -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 2.5 퍼블릭 상수를 사용하지 마라. 2 | 객체들은 어떤 것도 공유해서는 안된다.
3 | 독립적이어햐 하고 '닫혀 있어야(closed)'한다.
4 | 5 |
6 | 7 | 예를 들어 A 클래스와 B 클래스가 동일한 이름의 C라는 퍼블릭 상수를 각각 가지고 있다고 해보자.
8 | 이 코드는 중복이 되는데, 어떻게 중복을 제거할 수 있는가?
9 | 10 |
11 | 12 | 이러한 점을 제거하기 위해 C와 관련된 클래스를 하나 만들어 그 안에 퍼블릭 상수를 넣어줄 수도 있다.
13 | 그러면 A, B클래스는 C 클래스의 퍼블릭 상수를 가져 사용하면 된다.
14 | 하지만 이렇게 되면 결합도가 높아지고, 응집도가 낮아지게 된다.
15 | 16 |
17 | 18 | 결합도가 높아졌다. : C 클래스의 퍼블릭 상수가 수정되면 A, B는 모두 영향을 끼친다.
19 | 응집도가 낮아졌다. : C 클래스 안의 퍼블릭 상수는 스스로가 자율적으로 무언가를 하지 않는 텍스트 덩어리가 되었다.
20 | 이 대안으로는 C 클래스의 퍼블릭 상수를 사용하는 것처럼 행위를 가지는 클래스를 만든다.
21 |
22 | 가령 A, B 클래스에서 C 클래스의 퍼블릭 상수를 추가하여 주는 행위를 가진다면, 그 행위의 책임을 가지는 클래스를 만들어주는 것이다.
23 | 이는 계약에 의해 추가된 것이면 언제든 분리가 가능하기 때문에 유지보수성을 저하시키지도 않는다.
24 | 25 |
26 | 27 | ```java 28 | // 상수를 사용하는 코드 29 | new HttpRequest() 30 | .method(HttpMethods.POST) 31 | .fetch(); 32 | 33 | // HTTP 메서드를 표현하는 단순 클래스를 만든다.(`HttpRequest()`) 34 | new HttpRequest(new HttpRequest()) 35 | .fetch(); 36 | ``` 37 | 38 |
39 | 40 | # 느낀점 41 | 42 | 개인적으로 나는 상수를 사용하는 것이 읽기는 편하다.
43 | 하지만 예고르씨 말대로 결합도나 응집도의 측면에서는 퍼블릭 상수를 쓰지 않는 것이 올바르다는 생각이 든다.
-------------------------------------------------------------------------------- /4장-은퇴/2절-체크_예외만_던지세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 체크 예외만 던지세요 2 | 3 | ## 느낀 점 4 | - 앞서 클린코드에서 Unchecked예외만 던지라는 글을 읽은 적 있다. 특정 클래스에 의존하는 다른 클래스들로 예외 시그니처가 전달되고, 그로 인해 발생하는 결합도가 유지보수성에 악영향을 끼치기 때문 이라고 이해했다.... 5 | - 다시 읽어보니 위의 이유로 OCP를 위반하게 되고(시그니처 변경에 따라 다른 클래스들이 영향을 받으므로) 모든 상위 메소드들이 최하위 메소드의 예외 시그니처를 알아야 하므로 캡슐화가 깨진다는 내용이다. 6 | - 이후 우테코에서 객체지향에 대해 배우며 해당 내용에 대해 이해했다. 그리고 checked 예외를 던지는 라이브러리를 사용할 때 불편함을 직접 겪으면서 checked예외는 사용하지 말아야 겠다는 생각이 자리잡았다. 7 | - checked예외를 만나서 겪은 불편함은 다음과 같다 8 | 1. 예외를 throw한 메소드와 3depth만 멀어져도 이 예외의 발생 근원지를 추측하기가 쉽지 않다. 어디에서 어떤 오류가 발생하는지에 대한 맥락을 알아야 예외처리가 가능한데, 어디서 발생하는지도 모르고 그저 시그니처만 올라온다면 예외처리를 해야 한다는 의욕이 사라지고 내가 이해하지못한 예외에 대한 처리를 해도 되나? 하는 겁부터 난다. 결국 최상위까지 throw하게 된다. 9 | - 그런 연유로 하야 처음 읽을 때는 이 아저씨 또 투머치한 이야기 하신다, 생각을 했으나 정리를 위해 다시 읽으니 새로운 느낌이 들었다. 10 | - 각 클래스, 메소드의 시그니처는 아주 명확해야 한다. 조정자와 빌더에 대해서 강조하던 내용을 기억하시는지? 이렇게 각 책임과 역할이 드러나는 것을 중시하는 입장에선, 예외가능성이 드러나지 않는 건 클라이언트를 속이는 것처럼 느껴질거라 생각한다. 11 | - 읽으면서 든 여러가지 반론에 대해, 예외 체이닝과 AOP를 활용한 반복 처리, 그리고 main문에서 catch하기 등 좋은 대답을 했다고 생각한다. 그러나 객체지향적으로 아름다운 것과는 별개로, 거의 대부분에 클래스와 메소드에 Exception이 정의되어 있고, 예외처리를 위한 AOP가 적용된 코드가 가독성이 좋다고 생각지는 않는다. 책에 나온 방식은 팀원을 배려하지 않은 나만의 만족이라는 생각이 든다. 12 | - 그러므로 나는 앞으로도 Unchecked예외를 throw할 예정이다. 13 | -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/bepoz.md: -------------------------------------------------------------------------------- 1 | # 2.6 불변 객체로 만드세요 2 | 3 | ## 내용 4 | 5 | * 불변성은 크기가 작고, 응집력이 높으며, 느슨하게 결합되고, 유지보수하기 쉬운 클래스를 만들 수 있도록 한다. 6 | * 불변 객체는 필요한 모든 것을 내부에 캡슐화하고 변경할 수 있도록 통제한다. 불변 객체를 수정해야 한다면 프로퍼티를 수정하는 대신 새로운 객체를 생성해야 한다. 7 | * 가변 객체의 사용을 엄격하게 금지해야 한다고 주장한다. 8 | * 식별자 가변성으로 인한 문제가 있기에 불변을 유지해야 한다. 9 | * 불변객체를 사용한다면 해당 객체가 들어간 메서드에서 다른 비즈니스 코드가 이어지고 나서도 값이 불변하다는 것을 확실시 할 수 있기 때문에 이해하기가 더 쉽다. 10 | * 부수효과를 막을 수 있다. 11 | * 모든 객체를 불변으로 만들면 객체 안에 null을 포함시키는 것이 불가능해지기 때문에 응집도 높은 객체를 생성할 수 밖에 없게되어 유지보수하기 더 쉬운 객체가 된다. 12 | * 스레드 안정적이다. 13 | * 단순해지기 때문에 유지보수하기 더 수월해진다. 14 | 15 |
16 | 17 | ## 생각 및 의문점 18 | 19 | 불변 객체가 좋다는 것은 인정한다. 이번 장 내용에서 크게 말할 것이 없다. 20 | 21 | 살짝 논외로 2.4의 ``메서드 이름은 신중하게 선택하세요.`` 파트에서 조앤의 [PR](https://github.com/woowacourse-elegant-object/elegant-object-study/pull/30) 에 다음과 같은 물음이 있었다. 22 | 23 | > 그리고 불변객체의 경우! 불변 객체에서는 조정자가 새로운 객체를 반환한다. 그럴 땐 어떻게 하라는겨! 24 | 25 | 그리고 이번장 66p에서 다음과 같은 코드가 적혀져 있다. 26 | 27 | ```java 28 | class Cash { 29 | private final int dollars; 30 | 31 | public Cash mul(int factor) { 32 | return new Cash(this.dollars * factor); 33 | } 34 | } 35 | ``` 36 | 37 | 조정자인데도 불구하고 값을 반환하고 있다. 불변객체는 예외적인 것일까?? 예고르 되게 엄격한척 하면서 사실은 융통성 있는 사람이었던걸까?? 38 | -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 1.1 -er로 끝나는 이름을 사용하지 마세요 2 | 3 | ## 정리 4 | - 클래스는 객체의 팩토리(factory)이다. 5 | - 클래스는 객체를 인스턴스화(instantiate)한다. 6 | - 클래스는 필요할 때 객체를 꺼낼 수 있고, 더 이상 필요하지 않은 객체를 반환할 수 있는 객체의 웨어하우스(warehouse) 같은 존재이다. 7 | - 팩토리 패턴 ≒ new 연산자 8 | ``` 9 | Class Shapes { 10 | public Shape make(String name) { 11 | if (name.equals("circle") { 12 | return new Circle(); 13 | } 14 | if (name.equals("rectangle") { 15 | return new Rectangle(); 16 | } 17 | throw new IllegalArgumentException("not found"); 18 | } 19 | } 20 | ``` 21 | - 클래스의 이름은 클래스가 무엇인지(what he is)에 기반해서 지어야 한다. 무엇을 하는지(what he does)에 기반하면 안 된다. 22 | - ex. Cash, USDCash, CashInUSD (O) 23 | - ex. CashFormatter (X) 24 | - 객체는 그의 역량(capability)으로 특정지어져야 한다. 25 | - 객체는 캡슐화된 데이터의 대표자(representative)이다. 연결장치(connector)가 아니다. 26 | - 내가 누구인지 ≠ 내가 무엇을 하는지 27 | - ex. 리스트(내가 누구인지)는 인덱스 번호를 통해 특정 위치의 요소를 선택할 수 있다(내가 무엇을 하는지). 28 | - ex. SQL 레코드(내가 누구인지)는 임의의 셀에 저장된 정수값을 조회할 수 있다(내가 무엇을 하는지). 29 | 30 | ## 느낀점 31 | - 하나의 언어에 국한되지 않고 다양한 언어로 예시를 보여줘서 코드를 읽는 시각을 조금이나마 넓힐 수 있었다. 32 | - 책의 저자는 클래스 이름을 -er, -or로 끝내지 말라고 주장하며, Controller도 네이밍이 잘못됐다고 말한다. 근데, Controller처럼 관례로 사용되는 네이밍도 굳이 피해야 하나? 의문점이 든다. 33 | -------------------------------------------------------------------------------- /4장-은퇴/1절-절대_NULL을_반환하지_마세요/nabom.md: -------------------------------------------------------------------------------- 1 | 객체에 대한 신뢰가 있어야한다. 과연 NULL을 반환하는 객체는 신뢰가 있다고 볼 수 있을까? 2 | 3 | 하지만 설계자들은 exception을 던지는 대신 NULL을 반환하게 했을까? 그것은 빠르게 실패하기 원칙을 몰랐기 떄문이라고 추측한다. 4 | 5 | ### 빠르게 실패하기 vs 안전하게 실패하기 6 | 7 | **안전하게 실패하기**는 버그, 입출력 문제, 메모리 오버플로우 등이 발생한 상황에서도 소프트웨어가 계속 실행될 수 있도록 최대한 많은 노력을 기울일 것을 권장한다. 그러므로 NULL을 반환하는 방법도 일종의 생존 기법이라 볼 수 있다. 8 | 9 | **빠르게 실패하기**는 일단 문제가 발생하면 곧바로 실행을 중단하고 최대한 빨리 예외를 던진다. 결과에 대해서는 걱정하지 않는다. 즉, 모든 실패 지점이 명확해질 수 있는 이익을 얻을 수 있다. 10 | 11 | 즉, 에러를 발견하는 동시에 에러를 대처하는 방법(빠르게 실패하기)을 필자는 더욱 선호한다. 12 | 13 | **** 14 | 15 | ### NULL 의 대안 16 | 17 | 개인적으로 가장 null 대응하기 힘들었던 부분은 데이터베이스에서 `List` 를 찾아오는 경우였다. 값을 하나만 가져오는 경우에는 `Optional` 을 사용할 수 있지만 `List` 를 가져올 때는 이 방법을 사용하기 힘들다. 이를 위한 코드이다. 18 | 19 | ```java 20 | public Collection users(String name) { 21 | if (/* 데이터베이스에서 발견하지 못했다면 */) { 22 | return new ArrayList<>(0); 23 | } 24 | return Collections.singleton(*/데이터베이스로부터*/); 25 | } 26 | ``` 27 | 28 | 만일 null과 비슷한 역할을 하고 싶은 애를 반환하고 싶다면 Null객체를 직접 만들어 사용하자. 29 | 30 | ```java 31 | class NullUser implements User { 32 | @Override 33 | public void raise(Cash salary) { 34 | throw new IllegatStateException("봉급 인상 불가능! null입니다."); 35 | } 36 | } 37 | ``` 38 | 39 | ### 느낀점 40 | 완전 동의하는 장이다!!! -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 2. 생성자 하나를 주 생성자로 만드세요. 2 | 하나의 클래스는 2\~3개의 메서드와 5\~10개의 생성자를 포함하는 것이 적당하다. 응집도가 높고 견고한 클래스에는 적은 수의 메서드와 상대적으로 더 많은 수의 생성자가 존재한다. 생성자가 많을수록 클래스는 더 개선되고, 사용자 입장에서 더 편하게 해당 클래스를 사용할 수 있다. 메서드가 많아지면 클래스의 초점이 흐려지고, 단일 책임 원칙을 위반한다. 생성자가 많아지면 유연성이 향상된다. 3 | 4 | ``` 5 | new Cash(30); 6 | new Cash("$30"); 7 | new Cash("30d"); 8 | new Cash("30", "USD"); 9 | ``` 10 | 11 | 생성자의 주된 작업은 제공된 인자를 사용해 캡슐화하고 필드를 초기화하는 일이다. 12 | 이러한 초기화 로직을 단 하나의 생성자에만 위치시키고, 주생성자라고 부르기를 권장하며, 부 생성자라고 부르는 다른 생성자들이 이 주 생성자를 호출하도록 만들자. 13 | 14 | ``` 15 | class Cash { 16 | private int dollars; 17 | Cash (float dlr) { 18 | this((int) dlr); 19 | } 20 | Cash (String dlr) { 21 | this(Cash.parse(dlr)); 22 | } 23 | Cash (int dlr) { 24 | this.dollars = dlr; 25 | } 26 | } 27 | ``` 28 | 29 | ### 내 의견 30 | 앞서 1.1에서 주장하는 -er을 사용하지 마세요.에 대해서는 약간 반대도 아니고.. 동의도 아닌 애매한 입장이었다. 하지만 1.2를 읽고 보니, 1.2에서 말하는 대로 클래스를 설계한다면 1.1은 자연스럽게 지켜질 수도 있겠다는 생각이 들었다. 31 | 그 이유는 내가 설계하는 클래스들은 대부분 내부 메서드가 많고, 생성자가 적은 응집도가 낮은 클래스들이었다. 😅 (갑자기 반성) 만약 생성자가 많고, 내부 메서드가 적다면 그 클래스는 기능이 아닌 무엇인지에 기반한 명명이 가능할 것 같다. 32 | 아무튼, 이번 1.2는 나도 동의한다. 여러 생성자가 많으면 유연하게 사용할 수 있어 편리한 점이 많고, validation 또한 한 곳에서 할 수 있기 때문에 응집도가 높아진다고 생각한다 🧐 33 | -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/koda.md: -------------------------------------------------------------------------------- 1 | # 2.5 퍼블릭 상수(Public Constant)를 사용하지 마세요 2 | 3 | ## 내용 4 | 5 | 코드의 중복을 줄이기 위해서 public constant를 자주 사용하는데 관련하여 두가지 문제가 발생하기 때문에 사용을 지양해야 한다. 6 | 7 | 1. 결합도 증가 8 | 9 | - 중복을 줄이기 위해 어느 한곳에 선언된 퍼블릭 함수를 다른 클래스 여기저기서 공용으로 사용하게 된다면 그 다른 클래스들은 퍼블릭 상수가 선언된 객체와 결합되어 의존하는 것이다. 10 | 11 | 2. 응집도 감소 12 | 13 | - 객체가 스스로 문제를 해결하는 것이 아니라 외부에 의존하여 문제를 해결하면 응집도가 감소된다. 14 | 15 | - 상수는 의미없이 그냥 하드코딩된 어떠한 데이터 덩어리일 뿐이다. 그렇기 때문에 이러한 데이터를 공유하는 것이 아니라 기능을 하는 능동적인 객체로 **기능을 공유**해야 한다. 16 | 17 | ```sql 18 | //한 줄을 추가하는 기능을 공유하고 싶을 경우 퍼블릭 함수로 /r/n을 추가하지 마라 19 | class EOLString { 20 | private final String origin; 21 | EOLString(String src) { 22 | this.origin = src; 23 | } 24 | 25 | @Override 26 | String toString() { 27 | return String.format("%s\\r\\n", origin) 28 | } 29 | } 30 | ``` 31 | 32 | 즉, 퍼블릭 상수가 의미하는 계약을 준수하는 **새로운 클래스**를 만드는 것이 적절하다. 33 | 34 | ## 의견 35 | 36 | - 작가가 예시로 든 방식으로 구현하는 것을 처음 봐서 굉장히 신선했다. 그리고 퍼블릭 상수가 어떠한 의미나 계약을 준수하는 무엇인가로 생각하기보다 작가가 말했던 것처럼 반복되는 것을 줄이고자 하는 하드코딩 덩어리처럼 내 스스로도 여겼다는걸 알게 되었다. 37 | - 꽤 재미있는 방식의 구현이어서 다음에 다른 것을 구현할 때 한번 적용해보고 싶다. 38 | - 객체지향이라는 것은 모든 것을 계약을 준수하는 능동적인 객체의 집합으로 보고 상호 협력하도록 하는 것이라는 것을 다시한번 알게 된 것 같다. -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 2.8 모의 객체(mock) 대신 페이크 객체를 사용한다. 2 | 3 | 테스트를 최적화하기 위한 도구, 모킹(mocking)
4 | 5 | 6 | ```java 7 | Exchange exchange = Mockito.mock(Exchange.class); 8 | Mockito.doReturn(1.15) 9 | .when(exchange) 10 | .rate("USD", "EUR"); 11 | Cash dollar = new Cash(exchange, 500); 12 | Cash euro = dollar.in("EUR"); 13 | assert "5.75".equals(euro.toString()); 14 | ```` 15 | 16 | 모킹은 다음과 같이 사용한다.
17 | 하지만 예고르씨는 모킹 대신 '페이크 객체(Fake Obejct)'를 사용할 것을 권한다.
18 | 19 | ```java 20 | interface Exchange { 21 | float rate(String origin, String target); 22 | final class Fake implements Exchange { 23 | @Override 24 | float rate(String origin, String target) { 25 | return 1.2345; 26 | } 27 | } 28 | } 29 | 30 | Exchangeb exchange = new Exchange.Fake(); 31 | Cash dollar = new Cash(exchange, 500); 32 | Cash euro = dollar.in("EUR"); 33 | assert "6.17".equals(euro.toString()); 34 | ``` 35 | 36 | 페이크 클래스를 사용하게 되면 테스트를 더 짧게 만들 수 있기 때문에 유지 보수성이 향상된다.
37 | 반면 모킹은 테스트 코드가 길어지고 이해하기 어려워 리팩토링에 어려움이 생길 수 있습니다.
38 | 39 | 모킹을 사용해 본적이 없어서 제대로 이해했는 지 모르겠지만,
40 | 예고르씨 말대로 페이크 클래스 내부에서 테스트코드를 작성해준다고 가정해보았을 때 테스트 코드가 많아질 수록 내부 클래스 코드가 길어질 텐데 그런 부분도 고려하고 저렇게 하는 것이 좋다고 판단한걸까 🤔
-------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/cu.md: -------------------------------------------------------------------------------- 1 | # 2.6 불변 객체로 만드세요 2 | 3 | > https://brainbackdoor.tistory.com/141 4 | 5 | 6 | ### 불변객체 이점 7 | 8 | 1. 불변 객체를 사용하면 식별자 가변성 문제가 발생하지 않는다. 가령 아래와 같이 Map을 사용할 때 요소의 상태를 변화시켜 키를 중복상태로 만들 수 있다. 9 | 10 | ```java 11 | Map map = new HashMap(); 12 | Cash five = new Cash("$5"); 13 | Cash ten = new Cash("$10"); 14 | map.put(five, "five"); 15 | map.put(ten, "ten"); 16 | five.multiple(2); 17 | System.out.println(map); // {$10=>"five", $10=>"ten"} 18 | ``` 19 | 20 | 2. 불변객체를 사용하면 실패원자성을 확보할 수 있다. 즉, 견고한 객체이거나 실패하거나 두가지 경우만 존재한다. 21 | 22 | 3. 시간적 결함을 제거할 수 있다. 객체의 상태를 변경시키는 시점에 따라 결과가 달라지는 경우, 데이터의 흐름을 머릿속에 그려가며 프로그래밍을 해야 한다. 이런 절차지향적인 사고를 막을 수 있다. 23 | 24 | ```java 25 | Cash price = new Cash(); 26 | // 50 줄의 코드 27 | price.setDollars(29); 28 | System.out.println(price); // "$29.00" 29 | // 30 줄의 코드 30 | price.setCents(95); 31 | System.out.println(price); // "$29.95" 32 | ``` 33 | 34 | 4. Side effect를 제거할 수 있다. 이는 Thread safe와 함께 생각해봐도 좋다. Multi Thread 환경에서 공유가변데이터가 존재할 경우 동기화처리가 제대로 되지 않으면 여러 부수효과가 발생할 수 있다. 이걸 모두 생각하며 프로그래밍하는 것보다는 불변객체를 사용하는 것이 실용적이다. 35 | 36 | 5. 불변객체를 사용하여 인스턴스화할 때 상태를 초기화하도록 강제하면, NULL 참조를 할 수 없어 좋은 설계를 유도할 수 있다. 37 | 38 | - 단순함을 보다보니 심플 소프트웨어가 떠오른다.. 39 | > http://www.yes24.com/Product/Goods/80749963 40 | -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/nabom.md: -------------------------------------------------------------------------------- 1 | 우리는 더 빠른 테스트를 위해 모의 객체(Mock)를 만들어 사용한다. 하지만 모킹은 'bad practice'이고 최후의 수단으로만 사용해야 한다. 모킹은 테스트가 매우 장황해지고 이해하거나 리팩토링하기 어려워진다. 하지만 페이크 객체(Fake)를 사용하게 되면 테스트가 더 짧아지고 유지보수성이 눈에 띄게 향상된다. 페이크 객체를 생성하는 방법은 다음과 같다. 2 | 3 | ```java 4 | interface Exchange { 5 | float rate(String origin, String target); 6 | final class Fake implements Exchange { 7 | @Override 8 | float rate(String origin, String target) { 9 | return 1.234; 10 | } 11 | } 12 | } 13 | ``` 14 | 15 | 페이크 객체 접근 방식은 다음과 같다. `Exchange exchange = new Exchange.Fake();` 16 | 17 | 단위 테스트만을 위해 또 다른 과정을 추가할 수도 있고 매우 유연해지는 코드이다. 최대한 인터페이스를 제공한다면 페이크 객체도 같이 제공해 아름다운 테스트 코드를 만들자! 18 | 19 | ### 느낀점 20 | 21 | 인터페이스를 사랑하는 것과 테스트와 프로덕션은 하나다 라는 신념을 가지고 있는 예고르 형님의 철학을 잘 볼 수 있었던 장이었던 것 같다. 22 | 23 | 하지만, 목 객체와 페이크 객체는 좀 다르게 봐야되는 것 같다. 페이크 객체는 만들어 두면 고정 값만 리턴해주고 목 객체는 테스트에서 필요한 때에 원하는 값을 리턴해주는 차이가 있는 것 같다. 예를 들어 페이크 객체는 1.234 만 리턴하는데 테스트 코드에서는 100 이상의 값을 테스트하고 싶을 때이다. 24 | 25 | 또한, 정말 테스트 코드와 프로덕션 코드는 하나일까? 현실적으로는 불가능한 것 같다. 테스트 빌드 방식과 프로덕션 코드 빌드 방식, 테스트와 프로덕션의 서로 다른 라이브러리 의존성(MockMvc), 서로 다른 테스트 환경(H2 in memory) 등등, 이미 많은 곳에서 둘의 개념을 분리한다. 그렇다면 테스트 의존적인 페이크 객체를 프로덕션 코드에 둬도 괜찮을 걸까? 프로덕션 코드만큼 테스트 코드의 중요성이 크다는 것은 동의하지만 둘은 엄연히 분리해야하는 개념같다. 즉, 프로덕션 코드에 테스트를 위한 코드는 지양하는 것이 좋은 것 같다. -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/koda.md: -------------------------------------------------------------------------------- 1 | # 2.6 불변 객체로 만드세요 2 | 3 | ## 내용 4 | 5 | ### 식별자 가변성 6 | 7 | 객체가 불변하지 않다면 해당 객체의 식별자인 필드가 변하면서 관리가 어려워진다. 8 | 9 | 코드의 어느 부분에서 해당 객체가 변하고 현재 객체의 식별자가 어느 상태인지 추적하기가 매우 어렵다 10 | 11 | ### 식별 원자성 12 | 13 | 실패의 원자성은 완전한 상태의 객체를 가지거나 실패하거나 둘 중 하나만 가능하도록 한 것이다. 14 | 15 | 가변 객체의 경우 특정 메서드에서 변경하는 로직 중간에 예외가 일어나게 되면 일부 프로퍼티가 바뀐 상태로 절반만 변경이 된 상태가 되어버린다. 따라서 불완전한 객체가 생겨버린다. 하지만 불변 객체는 내부의 것을 수정하기보다 새로운 객체를 반환하기 때문에 원자성을 보장한다. 16 | 17 | ### 시간적 결합 18 | 19 | 시간적인 결합에 자유롭기 때문에 코드의 순서를 신경쓰면서 구현을 하지 않아도 되고 따라서 유지보수에 용이하다. 즉 불변객체는 인스턴스화와 초기화가 분리되지 않기 때문에 그 사이의 시간적인 결합을 제거할 수 있다. 20 | 21 | ### 부수효과 제거 22 | 23 | 누구나 해당 객체에 접근해서 변경할 수 있기 때문에 부수적인 오류가 발생할 가능성이 높다. 24 | 25 | ### Null 참조 없애기 26 | 27 | 불변객체는 인스턴스화와 초기화가 분리되지 않기 때문에 프로퍼티의 값이 Null로 할당될 수 없다. 따라서 해당 객체가 유효한 상태인지, 어떠한 상태인지 고민할 필요가 없기 때문에 유지보수에 용이하다. 28 | 29 | 만일 초기화되지 않은 프로퍼티를 가진 객체가 필요할 때는 다른 클래스를 생성해서 분해하도록 구현해야 한다. 30 | 31 | ### 스레드 안정성 32 | 33 | 불변객체는 여러 스레드에서 동시에 사용되면서 그 결과가 불변하다는 것을 보장할 수 있기 때문에 예측가능하도록 유지할 수 있다. 34 | 35 | ### 더 작고 더 단순한 객체 36 | 37 | 불변객체는 크게 만들기 어렵기 때문에 단순하고 작은 객체를 설계하게 된다. 왜냐하면 불변객체는 생성자 안에서만 프로퍼티를 초기화할 수 있기 때문에 지나치게 많은 프로퍼티를 만들어서 한꺼번에 초기화하지 않도록 구현한다. 38 | 39 | 40 | 41 | ## 의문 42 | 43 | - 불변객체를 보장하기 위해서 새로운 객체를 생성하여 리턴하는 방식을 추천하고 있다. 그런데 그렇게 새로운객체를 리턴하면서 언젠가 가변인 부분이 있지 않을까? -------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/koda.md: -------------------------------------------------------------------------------- 1 | # 3.2 정적 메서드를 사용하지 마세요 2 | 3 | ## 내용 4 | 5 | - 정적 메서드 대신 객체를 사용하는 것이 객체지향적이다! 6 | - 그 이유는 정적 메서드를 사용하는 것은 소프트웨어의 유지보수를 어렵게 하기 때문이다. 7 | 8 | 1. 객체지향 프로그래밍이 차별화되는 것은 'is a' 이다. 할 일을 지시하기보다 그냥 정의하는 것이다. 9 | 10 | ```java 11 | //객체지향적인 정의 12 | Number x = new Max(5, 9); 13 | 14 | //절차지향적인 정적 메서드 사용 15 | int x = Math.max(5, 9); 16 | ``` 17 | 18 | 2. 제어 흐름을 서술하는게 아니라 계산 로직을 표현하는 선언형 프로그래밍을 사용해야한다. 19 | 20 | - 정적 메서드에서 수정이 필요할 때 그 부분을 반드시 구현해야만 한다. 21 | - 선언형으로 구현한다면 다음 두 가지가 더 좋다. 22 | 1. 선언만 하고 연산을 뒤로 미뤄서 최적화가 가능하다. 23 | 2. 객체간의 결합도를 낮춘다. 24 | 3. 표현력이 좋다. (실행과정을 감추고 분명히 표현한다) 25 | 4. 코드 응집도를 높인다. (한줄에 선언하여 표현하기 때문에 순서의 제약을 받지 않고 시간적 결합문제가 없다) 26 | 27 | 3. 유틸리티 클래스가 된다 → 인스턴스를 생성하지 않게 된다. 28 | 29 | - 유틸리티 클래스는 하드코딩이라 클래스를 분리할 수 없는 의존성이다. 30 | 31 | 4. 싱글톤은 전역변수와 같은 역할을 하는 제대로된 객체가 아니다. 32 | 33 | - 캡슐화를 위반한다. 34 | 35 | 5. 함수형 프로그래밍보다 OOP가 표현력이 더 뛰어나다. 36 | 37 | 6. 객체를 생성하면 조합이 가능해진다. 하지만 정적 메소드는 조합이 불가능하다. 38 | 39 | ## 의견 및 의문 40 | 41 | - 항상 정적 메소드에 대해서 객체지향적이지 않기 때문에 쓰지 말라는 이야기만 들었는데, 다소 어려운 내용으로 왜 쓰지 말아야하는지에 대해서 알게 된 것 같다. 42 | - 정적 메소드를 지양해야 하는 이유 중 가능 큰 것은 정적 메소드는 하드코딩과 같아서 다형성을 추구할 수 없다는 것이 가장 큰 것 같다. 그러면 유지보수가 힘들어지고 해당 정적 메소드가 여러 곳에 퍼져있을 때 더더욱 힘들다. 43 | - 하지만 정적 메소드는 너무 편리하고 간단하다... -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/nabom.md: -------------------------------------------------------------------------------- 1 | `public static final ~~` '상수'라고 불리는 이 프로퍼티는 객체 사이에 데이터를 공유하기 위해 사용하는 유명한 메커니즘이지만 위험하다. 물론 코드 중복이라는 하나의 문제를 해결하지만 결합도가 높아지고 응집도는 낮아지는 문제점이 생긴다. 2 | 3 | 이러한 문제를 각각의 단순한 클래스를 많이 만들어 사용해서 해결한다. 4 | 5 | Enum 또한 메커니즘이 퍼블릭 상수와 같기 때문에 동일한 규칙이 적용된다. 6 | 7 | ## 느낀점 8 | 9 | 값은 확실히 객체를 통해 이야기해야 한다는 것은 우테코에서 훈련을 하다보니 공감이 갔다. 퍼블릭 상수를 외부에서 사용한다는 것은 나의 설계를 한 번 의심해야했고 여지없이 리팩토링이 가능했다. 10 | 11 | 하지만 외부 api(대표적으로 스프링)를 사용하면서 필요한 값을 주입할 때 퍼블릭 상수로 인해 많은 편의성을 누렸다. 엘레강트 오브젝트 책에서는 예시를 `HttpRequest` 로 들었다. 12 | 13 | `new HttpRequest().method(HttpMethods.POST).fetch()` 이 부분을 `new PostRequest(new HttpRequest()).fetch()` 처럼 바꾸게끔 권장한다. 이 부분은 공감하기 힘들다. 14 | 15 | 먼저 이 코드를 살펴보면 첫 번째 코드는 책처럼 읽힌다. httpRequest가 필요한데 포스트 방식이 필요하구나! 라는 형식으로. 두 번째 코드는 결론부터 알아야 하는 느낌이랄까..? 16 | 17 | 그리고 IDE의 도움을 받는 방식은 전자가 확실히 편하다. 가끔 `method()` 안에 어떤 값이 필요한 지 기억이 안날 때는 `HttpMethods` 까지 쓰고 . 을 찍어보고 IDE에서 보여주는 상수값을 통해 필요한 값을 적어서 보낸다. 하지만 모든 것을 객체로 쓰게되면 클래스 네임을 정확히 모르면 원하는 것을 사용하기 힘들다. 18 | 19 | 테스트 코드를 작성할 예상하는 content-type을 적을 때 `.contentType(MediaType.APPLICATION_JSON)` 이러한 코드를 사용하는데 MediaType에 대한 정확한 명칭을 모를 때 일단 `MediaType` 까지 쓰고 . 을 찍어 찾게된다... 이러한 편의성은 포기 못한다!!!!! 20 | 21 | 이러한 편의성 때문에 나는 `Enum` 은 포기 못한다. 만일 IDE에서 원하는 구현 클래스 추천 기능이 생긴다면 `Enum` 을 포기하고 뭐든 객체로만 연락하겠지만 그 전까지는..... `Enum` 만세!!!!!!!!!! -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 2.5 퍼블릭 상수(Public Constant)를 사용하지 마세요 2 | 3 | ## 정리 4 | ``` 5 | public class Constants { 6 | public static final String EOL = "\r\n"; 7 | } 8 | ``` 9 | - 해당 클래스는 가시성이 public이기 때문에 클래스 로더에 의해 로딩된 모든 클래스들이 상수에 접근할 수 있다. 10 | - 이는 클래스의 결합도(coupling)을 높이고, 응집도(cohesion)을 낮춘다. 👉 좋지 않은 설계이다. 11 |
12 | 13 | ``` 14 | class EOLString { 15 | private final String origin; 16 | 17 | EOLString(String src) { 18 | this.origin = src; 19 | } 20 | 21 | @Override 22 | String toString() { 23 | return String.format("%s\r\n", origin); 24 | } 25 | } 26 | ``` 27 | - 객체는 데이터가 아니라 기능을 공유해야 한다. 28 | - 따라서, 기능을 공통으로 제공할 새로운 클래스를 추가한다. 29 |
30 | 31 | - Constants 클래스에 결합되는 것과 EOLString 클래스에 결합되는 것 사이에는 큰 차이가 없어 보인다. 32 | - 그러나, EOLString에 대한 결합은 계약(contract)를 통해 추가됐다. 33 | - 계약에 의한 결합은 분리가 가능하기 때문에 유지보수성을 저하시키지 않는다. 34 | - 즉, EOLString은 계약에 따라 행동하며 클래스 내부에 계약의 의미를 캡슐화한다. 35 |
36 | 37 | - 아무리 사소해 보이는 상수라도 항상 작은 클래스를 이용해서 대체해야 한다. 38 | - Java의 열거형(enum)에 대해서도 동일한 규칙이 적용된다. 39 | 40 | ## 느낀점 41 | 42 | - 그동안은 중복되는 상수를 각 클래스 안에 `private static final`로 여러 번 선언해서 사용했다. 43 | - 이번 주제를 읽고 중복되는 상수는 작은 클래스로 분리해서 활용하는 게 좋을 것 같다는 생각이 들었다. 44 | - 그런데, 굳이 enum도 작은 클래스 여러 개로 분리하는 게 맞는 방법일지는 모르겠다. 45 | - 오히려 클래스 수만 많아지고 복잡해질 것 같다. -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/koda.md: -------------------------------------------------------------------------------- 1 | # 2.8 모의 객체(Mock) 대신 페이크 객체(Fake)를 사용하세요 2 | 3 | ## 내용 4 | 5 | Mock 객체를 통해서 테스트를 하는 경우 다음과 같은 문제들이 발생한다. 6 | 7 | - 테스트가 장황하고 이해하기 어렵고 리팩토링하기 어려워진다. 8 | - 우선 Mock 객체에 대한 설정들을 테스트코드에서 해야한다. 9 | - 가정을 사실로 만들어 버린다. 10 | - Mock 객체를 설정할 때, 내부에서 사용하는 메소드를 밖에 노출한다. 11 | - 때문에 어떤 메소드 A를 호출할 때 사용하는 감추어진 메소드를 알게 한다. 12 | - 클래스의 행동이 변경되지 않았을 때에도 단위 테스트가 실패할 수 있다. 13 | 14 | Mock 객체는 테스트에 우호적이지 않다. 15 | 16 | 어떠한 클래스 A에 대한 테스트를 작성할 때 우리의 관심사는 클래스 A와 우리의 상호작용이지 다른 클래스와의 상호작용이 아니다. 그러나 모의객체는 그러한 상호작용까지 알게하고 신경쓰게 하여 유지보수를 어렵게 한다. 또한 캡슐화를 깨뜨린다. 17 | 18 | 그러므로 테스트를 할 때는 Fake 객체를 사용하자. 19 | 20 | ```java 21 | interface Exchange { 22 | float rate(String target); 23 | float rate(String origin, String target); 24 | 25 | final class Fake implement Exchange { 26 | @Override 27 | float rate(String target) { 28 | return this.rate("USD", target); 29 | } 30 | 31 | @Override 32 | float rate(Strin origin, String target) { 33 | return 1.2345; 34 | } 35 | } 36 | } 37 | ``` 38 | 39 | ## 의견 40 | 41 | Mock 객체에 대한 의구심을 가져본적이 없는데 예고르가 제시한 문제점들을 처음 알게 되어서 한층 시야가 넓어진 것 같다. 42 | 43 | 이전에 예고르가 말했던 것처럼 테스트 코드를 프로덕션 코드와 함께 중요한 한 일부분으로 보고 객체지향의 아이디어들을 모두 적용하며 깔끔하게 함께 짜야할 코드로 보는 것이 느껴졌다. 44 | 45 | 다음에 Fake 객체를 적용해보고 싶다! 그렇게 하려면 이전에 나왔던 거의 모든 클래스에 대해서 인터페이스를 자연스럽게 생성하게 될 것 같다. 그리고 매우 귀찮겠지 ... -------------------------------------------------------------------------------- /3장-취업/3절-인자의-값으로-NULL을-절대-허용하지-마세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 3.3 인자의 값으로 NULL을 절대 허용하지 마세요. 2 | 3 | 인자의 값으로 널은 무슨 일이 있어도 절대 허용하면 안된다. 4 | 값이 비어있다는 의미로 널을 전달하는 경우가 존재하는데, 그러한 경우 메서드는 다음과 같이 작성되어야한다. 5 | ``` 6 | public void temp(Obj obj) { 7 | if (obj == null) { 8 | sout("null"); 9 | } else { 10 | 11 | } 12 | } 13 | ``` 14 | 이렇게 널인지 확인을 하고, 널이 아닌 경우에만 로직을 수행하도록 한다면 이는 객체의 책임(자신의 존재여부를 스스로 판단하도록 하는)을 빼앗는 것과 같다. 15 | 16 | 그렇다면, `값이 없는 경우`에는 어떻게 처리해야할까? 17 | 객체의 상위에 인터페이스를 만든 뒤 존재하지 않는 객체를 의미하는 객체를 만든다. 다음과 같은 AnyFile은 어떠한 내부 로직도 포함하지 않고 어떤 파일을 전달하든 항상 true를 반환한다. 따라서 null 대신 anyfile 인스턴스를 생성해서 전달하면 된다. 18 | ``` 19 | class AnyFile implements Mask { 20 | @Override 21 | boolean matches(File file) { 22 | return true; 23 | } 24 | } 25 | ``` 26 | 27 | 하지만 외부에서 널이 들어오는 경우(클라이언트) 어떻게 처리해야할까? 28 | 1. 그냥 무시하고 널포인터예외가 던져지도록 냅둔다. - 널 넣은 니들 잘못임 29 | 2. if (obj == null) 을 추가한다. 30 | 31 | 32 | ## 의견 33 | - null 대신 Optional을 이용할 수 있다. Optional에게 그 책임을 맡기고 외부에서 확인만 해준다면, null을 없앨 수 있다고 생각한다. 34 | - 값을 넘겨줄 때 null이 아님을 보장하고 넘겨준다면 인자로 전달받은 메서드 내부에서 null인지 확인해줄 필요가 없다고 생각한다. 따라서! 나는 null이 생기는 경우 nullPointerException을 던지도록 하는 것이 낫다고 생각한다. 일일히 null인지 모두 확인해주는 것은 불필요하다고 생각한다. 메서드 내부에서는 메서드 내부의 로직만 잘 수행하면 되고, 인자가 비어있는지 아닌지에 대해서는 내부에서 확인할 필요 없이 믿고 사용해야한다고 생각한다. 35 | 36 | 3.2부터 책을 읽은 뒤 책을 안보고 글을 작성하고 있어서 그런지 조금 횡설수설하는 감이 크다.. 점점 나아지겠지 -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 인터페이스를 짧게 유지하고 스마트를 사용하세요 2 | 3 | 스마트 클래스를 사용하면 높은 응집도를 유지할 수 있다. 4 | 5 | ```java 6 | interface Exchange { 7 | float rate(String source, String target); 8 | final class Fast implements Exchange{ 9 | private final Exchange origin; 10 | 11 | public Fast(Exchange origin) { 12 | this.origin = origin; 13 | } 14 | 15 | @Override 16 | public float rate(String source, String target) { 17 | final float rate; 18 | if(source.equals(target)){ 19 | rate = 1.0f; 20 | }else{ 21 | rate = this.origin.rate(source,target); 22 | } 23 | return rate; 24 | } 25 | public float toUsd(String source){ 26 | return this.origin.rate(source, "USD"); 27 | } 28 | } 29 | 30 | final class Smart{ 31 | private final Exchange origin; 32 | 33 | public Smart(Exchange origin) { 34 | this.origin = origin; 35 | } 36 | 37 | public float toUsd(String source){ 38 | return this.origin.rate(source, "USD"); 39 | } 40 | public float eurToUsd(){ 41 | return this.toUsd("EUR"); 42 | } 43 | } 44 | } 45 | 46 | ``` 47 | 48 | 이거 어떻게 사용해야될지 잘 모르겠어요... 49 | 50 | -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/cu.md: -------------------------------------------------------------------------------- 1 | # 1.1 -er로 끝나는 이름을 사용하지 마세요 2 | - 클래스는 객체의 팩토리이다. 즉, 클래스가 객체를 인스턴스화한다. 팩토리 패턴은 new 연산자를 확장한 것으로 보아도 좋다. 3 | - 그렇다면, 클래스의 이름은 어떻게 지어야 할까 4 | 객체를 살아있는 유기체로 본다면, 클래스 이름은 '무엇을 하는지'가 아니라, '무엇인지'에 초점을 두어야 한다. 5 | 보통 명사에 '-er'을 붙일 경우, 절차들의 집합으로 표현될 수 있다. (Validator, Converter 등) 6 | 7 | * Public Operation을 통해 외부에서 메시지를 전달하는 것이 익숙하다. 그런 관점에서 Method의 의도를 분명히 드러내도록 [Clean Code](https://kyounghwan01.github.io/blog/etc/how-to-write-clean-code/#%E1%84%92%E1%85%A1%E1%86%B7%E1%84%89%E1%85%AE%E1%84%86%E1%85%A7%E1%86%BC%E1%84%8B%E1%85%B3%E1%86%AB-%E1%84%83%E1%85%A9%E1%86%BC%E1%84%89%E1%85%A1%E1%84%85%E1%85%A9-%E1%84%86%E1%85%A1%E1%86%AB%E1%84%83%E1%85%B3%E1%86%AF%E1%84%85%E1%85%A1)에서 가이드하고 있다. 그러다보니 Class는 명사, Method는 동사를 쓰도록 가이드하고 있다. 하지만 이 책에서는 객체는 '메서드의 집합이 아님'을 강조한다. 8 | 9 | ```java 10 | object SlackMessageFormatter { 11 | fun makeRequestReview(reviewPair: ReviewPair, pullRequestView: GithubPullRequestView) = 12 | {...} 13 | fun makeRequestChange(reviewPair: ReviewPair, event: PullRequestReviewEvent) = 14 | {...} 15 | fun makeMerge(reviewPair: ReviewPair, event: PullRequestEvent, lastReview: String): String {...} 16 | ``` 17 | * 위와 같이 상태를 가지지 않는 클래스, 인자가 동일할 경우 항상 동일한 결과를 반환하는 함수의 경우에 유틸성 클레스를 작성하곤 했다. 위와 같은 요구사항이 있을 때 SlackMessage란 객체를 도출함으로써 어떤 이점이 있는걸까? 18 | > http://egloos.zum.com/kwon37xi/v/4844149 19 | -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 2.6 불변 객체로 만드세요 2 | 3 | ## 정리 4 | - 모든 클래스를 상태 변경이 불가능한 불변 클래스(immutable class)로 구현하면 유지보수성을 크게 향상시킬 수 있다. 5 | - 불변성(immutability)은 클래스를 크기가 작고, 응집력이 높으며, 느슨하게 결합되고, 유지보수하기 쉽게 만든다. 6 | 7 | ``` 8 | // 가변 클래스 9 | class Cash { 10 | private int dollars; 11 | 12 | public void setDollars(int val) { 13 | this.dollars = val; 14 | } 15 | } 16 | 17 | // 불변 클래스 18 | class Cash { 19 | private final int dollars; 20 | 21 | Cash(int val) { 22 | this.dollars = val; 23 | } 24 | } 25 | ``` 26 |
27 | 28 | - 불변 객체는 어떤 방식으로든 자기 자신을 수정할 수 없다. 29 | - 항상 원하는 상태를 가지는 새로운 객체를 생성해서 반환한다. 30 | 31 | ``` 32 | Cash five = new Cash(5); 33 | Cash fifty = five.mul(10); 34 | ``` 35 |
36 | 37 | - 불변 객체는 항상 하나의 문장만으로 객체를 인스턴스화할 수 있다. 38 | - 인스턴스화(instantiation)과 초기화(initialization)을 분리시킬 수 없다. 39 | 40 | ``` 41 | Cash price = new Cash(29, 95); 42 | ``` 43 |
44 | 45 | - 무엇보다 더 작고 더 단순한 객체로 만들기 위해 불변 객체로 만들어야 한다. 👉 단순성(simplicity) == 유지보수성 46 | - 객체가 단순할 수록 응집도는 더 높아지고 유지보수하기는 더 쉬워진다. 47 | - 또한 이해하고, 수정하고, 문서화하고, 지원하고, 리팩토링하기 더 쉬워진다. 48 | 49 | ## 느낀점 50 | 51 | - 우테코 초반에 강의를 들으면서 불변 객체에 대해 알게 됐고 사용하기 시작했다. 52 | - 그때는 단지 `final` 키워드만 추가한 것 같은데 어떤 점이 다른 걸까, 뭐가 나은 걸까 궁금했다. 53 | - 그래도 가변 객체보다 불변 객체가 좋다고 하니까 미션을 진행하며 무조건 불변 객체로 만들기는 했다. 54 | - 이번에 해당 주제를 읽으면서 불변 객체를 사용했을 때의 장점을 정리할 수 있었다. 55 | - 앞으로도 계속 가변 객체 말고 불변 객체로 만들어서 사용해야겠다~! -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 2.8 모의 객체(Mock) 대신 페이크 객체(Fake)를 사용하세요 2 | 3 | ## 정리 4 | - 모킹(mocking)은 bad practice이다. 👉 따라서, 페이크 객체(fake object)를 사용해야 한다. 5 | 6 | ``` 7 | // mocking 8 | Exchange exchange = Mockito.mock(Exchange.class); 9 | Mockito.doReturn(1.15) 10 | .when(exchange) 11 | .rate("USD", "EUR"); 12 | Cash dollar = new Cash(exchange, 500); 13 | Cash euro = dollar.in("EUR"); 14 | assert "5.75".equals(euro.toString()); 15 | 16 | // fake object 17 | interface Exchange { 18 | float rate(String origin, String target); 19 | 20 | final class Fake implements Exchange { 21 | @Override 22 | float rate(String origin, String target) { 23 | return 1.2345; 24 | } 25 | } 26 | } 27 | 28 | Exchange exchange = new Exchange.Fake(); 29 | Cash dollar = new Cash(exchange, 500); 30 | Cash euro = dollar.in("EUR"); 31 | assert "6.17".equals(euro.tostring()); 32 | ``` 33 | 34 | - 단위 테스트는 코드 리팩토링에 큰 도움이 된다. 👉 참 양성(true positive) 35 | - 동시에 행동이 변경되지 않을 경우에는 실패해서는 안 된다. 👉 거짓 양성(false positive) 36 | - 클래스의 공개된(public) 행동을 변경하지 않을 경우 실패하면 안 된다. 37 | - 모킹을 사용하면 단위 테스트가 너무 쉽게 깨지고 불안정해진다. 한편, 페이크 객체를 사용하면 단위 테스트가 훌륭하고 신뢰 가능해진다. 38 | - 그러므로 모킹을 사용하지 말고, 항상 인터페이스에 대해 `페이크 클래스`를 만들어야 한다. 39 | 40 | ## 느낀점 41 | 42 | - 이번 섹션을 읽으면서 페이크 객체라는 걸 처음 알게 됐다. 미션하면서 가능하면 한 번 쯤 사용해봐야겠다. 43 | - 어제 검프가 말한 대로 프로덕션 코드에는 영향을 미치지 않으니까 인터페이스 내에 페이크 클래스를 작성하는 건 괜찮은 방법인 것 같다. -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/gump.md: -------------------------------------------------------------------------------- 1 | # 1.1 -er로 끝나는 이름을 사용하지 마세요 2 | 3 | ## **정리** 4 | 5 | 클래스에 적합한 이름을 짓는 것은 중요해요. 클래스의 이름을 살펴보기 전, 클래스가 무엇인지 알아볼 필요가 있어요. 6 | 7 | ### **클래스는 동적인 객체의 상태와 행동을 정적인 텍스트로 표현하기 위한 추상화 도구** 8 | 9 | 말이 조금 어려워요. 좀더 쉽게 얘기하면, 클래스는 `객체의 어머니`라 볼 수 있어요. 10 | 11 | 즉, 필요할 때 생성되고, 더이상 필요하지 않은 객체는 반환하는 (어머니와 아이를 생각하면 조금 잔인할 수 있지만) 객체의 창고라고 말할 수 있어요. 12 | 13 | ### **클래스의 이름을 붙일 땐 `무엇을 하는지(what he does)`가 아닌 `무엇인지(what he is)`를 생각해요** 14 | 15 | 여기 숫자를 문자열로 포맷팅을하는 클래스가 있어요. 16 | 17 | ```java 18 | class NumberFormatter { 19 | private int number; 20 | 21 | NumberFormatter(int number) { 22 | this.number = number; 23 | } 24 | 25 | public String format() { 26 | return String.format("%d", this.number); 27 | } 28 | } 29 | ``` 30 | 31 | 숫자를 문자열로 변환하기에, 이 클래스는 더할나위 없이 완벽해 보여요. 32 | 33 | 하지만 이 클래스는 `무엇을 하는지`를 중점적으로 생각하기에, 연결장치에 불과하며 존중할만한 객체가 아니네요. 34 | 35 | ```java 36 | class Number { 37 | private int number; 38 | 39 | Number(int number) { 40 | this.number = number; 41 | } 42 | 43 | public String toString() { 44 | return String.format("%d", this.number); 45 | } 46 | 47 | public double toDouble() { 48 | return (double)number; 49 | } 50 | } 51 | ``` 52 | 53 | 존중한말한 객체로 변경했어요. 54 | 55 | 즉, 객체는 `그의 역량(capability)`로 특정지어져야 해요. 객체는 속성이 아니라 할 수 있는 일로 설명해야 해요. 56 | 57 | ## **의견** 58 | 59 | 역량을 생각하면 그가 할 수 있는 일이 따라온다는 관점이 멋저요. 이후 클래스 네이밍에 참고해야할 부분들이 많았어요. -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 1.3 생성자에 코드를 넣지 않는다. 2 | 3 | 주 생성자는 객체 초기화 프로세스를 시작하는 유일한 장소이기 때문에 제공되는 인자들이 완전해야한다고 말합니다. 4 | ('안자에 손대지 말라') 5 | 6 | ```java 7 | class Cash { 8 | private int dollars; 9 | 10 | Cash(String dlr) { 11 | this.dollars = Integer.parseInt(dlr); // 손댔음 12 | } 13 | } 14 | ``` 15 | 16 | 객체 초기화 시 코드가 없어야(code free)하고 인자를 건드려선 안됩니다. 17 | 필요하다면 인자를 다른 타입의 객체로 감싸거나, 가공하지 않은 형식으로 캡슐화해야 합니다. 18 | 19 | ```java 20 | class Cash { 21 | private Number dollars; 22 | Cash(String dlr) { 23 | this.dollars = new StringAsInteger(dlr); 24 | // 실제 사용 시점까지 객체 변환 작업을 연기한다! 25 | } 26 | } 27 | 28 | class StringAsInteger implements Number { 29 | private String source; 30 | StringAsInteger(String src) { 31 | this.source = src; 32 | } 33 | 34 | int intValue() { 35 | return Integer.parse(this.source); 36 | } 37 | } 38 | ``` 39 | 40 | 만약 첫번째 예제와 같은 경우로 사용하게 된다면, 41 | 객체를 만들 때마다 파싱이 수행되기 때문에 실행 여부를 제어할 수 없습니다. 42 | 반대로 인자가 전달된 상태 그대로 캡슐화하고 나중에 요청이 있을 경우 파싱하도록 하면, 클래스 사용자들이 파싱 시점을 자유롭게 결정할 수 있습니다. 43 | 44 | 물론 파싱이 단 한번만 수행되는 경우도 있을 것입니다. 45 | 하지만 '일관성'이라는 측면에서 미래에 어떤 기능이 추가될 지 모르기에, 미래의 리팩토링을 위해서 필요합니다. 46 | 결국 요지는 가벼운 생성자는 설정하기 쉽고 투명하게 사용할 수 있다는 것입니다. 47 | 48 | # 느낀점 49 | 50 | 마지막 문장을 보면서 저게 가벼워진건가?라는 생각도 들었다. 51 | 오히려 CashNumber를 Number로 포장하는 것을 보면서 포장지를 두겹으로 싸는 느낌이 들었다. 52 | 그리고 CashNumber에 로직이 들어가면 그때마다 intValue() 호출해서 파싱해야하는 것에도 의문점이 생겼다. 53 | 너무 객체지향적으로 생각하는 것은 오히려 독이 될 수도 있지 않을까? (아니면 예시가 적절하다고 보기 어려울 수도 있겠다.) 54 | 55 | -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/koda.md: -------------------------------------------------------------------------------- 1 | # 2.9 인터페이스를 짧게 유지하고 스마트(smart)를 사용하세요 2 | 3 | ## 내용 4 | 5 | 인터페이스는 다중 상속을 할 수 있기 때문에 인터페이스를 작게 유지하는 것이 클래스를 작게 유지하는 비결이다. 인터페이스에 너무 많은 메서드가 정의되어 있다면 SRP를 위반한 것과 마찬가지이다. 6 | 7 | 이때 인터페이스를 구현하는 구현체의 공통된 작업들을 구현하기 위해 Smart 클래스를 활용하자. 8 | 9 | ```java 10 | interface Exchange { 11 | float rate(String source, String target); 12 | 13 | final class Smart { 14 | private final Exchange origin; 15 | public float toUsd(String source) { 16 | return this.origin.rate(source, "USD"); 17 | } 18 | 19 | public float eurToUsd() { 20 | return this.toUsd("EUR"); 21 | } 22 | } 23 | } 24 | 25 | //사용시 26 | float rate = new Exchange.Smart(new NYSE()) 27 | .eurToUsd(); 28 | ``` 29 | 30 | 스마트 클래스의 크기는 더 커질 수 있지만 인터페이스의 크기 자체는 작게 유지된다. 또한 공통 기능이 인터페이스와 함께 베포되어 코드 중복을 피할 수 있다. 31 | 32 | ## 의견 및 의문 33 | 34 | - 이 챕터에서 말한 Smart 객체 또한 이전 챕터의 Fake 객체와 마찬가지로 처음 접하게 된 개념이라서 굉장히 흥미롭게 읽었다. 35 | - Smart 객체로 공통 부분을 추출하고 인터페이스를 작게 유지하는 것에 대한 아이디어에는 동의한다. 하지만 이렇게 inner 클래스를 활용하는 것이 익숙하지 않아서 그런지 이렇게 구현하는 것이 얼마나 편리할지에 대해서는 더 경험하고 말할 수 있을 것 같다. 36 | - Smart 클래스의 존재 여부, 그리고 어떠한 메소드가 Smart 클래스에 있을지 신경쓰고 소통하는 것에 대한 비용이 발생할 것 같다. 37 | - 더불어 Smart 클래스를 인터페이스의 메소드에 포함하지 않기 때문에 예고르가 말한 것처럼 '점점 더 커지는' 것에 대한 우려도 있을 것 같다. 모든 공통 메소드를 다 집어 넣어 배보다 배꼽이 큰 상황이 될 수도 있을 것 같다. 38 | - 개인적으로 이전에 미션을 진행할 때 `인터페이스 → 추상클래스에서 일부 공통 메소드 구현 → 해당 추상클래스를 구현하는 구현체 생성` 하는 방법으로 인터페이스의 공통 기능들의 중복을 줄인 것이 매우 편리하다고 느겼는데 Smart 클래스를 통해서 하는 것과 어떠한 차이가 있을까? 위의 설계가 Smart 클래스를 대체할 수 있는 걸까? -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 1.2 생성자 하나를 주 생성자로 만드세요 2 | 3 | 예고르씨는 올바른 클래스 설계는 많은 수의 ctor(생성자)와 적은 수의 메서드를 포함한다고 말합니다. (2번 강조 😲) 4 | 2 ~ 3개의 메서드와 5 ~ 10개의 ctor을 포함하는 것이 적당하다고 말하는데요, 5 | 응집도가 높고 견고한 클래스에는 적은 수의 메서드와 상대적으로 많은 수의 ctor가 존재한다는 것입니다. 6 | 7 | 단 하나의 ctor인 주(primary) 생성자와 이 주 생성자를 호출하는 부(secondary) 생성자를 만들어야합니다. 8 | 예고르씨는 유지 보수성을 위해 항상 주 생성자를 부 생성자 뒤에 위치시킨다고 하는데요 9 | 이는 예전에 만들어놓은 생성자를 하나하나 읽어가며 주 생성자를 찾기 힘들 때 맨 뒤에 위치시켜 유지 보수성을 높인다고 합니다! 10 | 11 | ### 하나의 주 생성자와 다수의 부 생성자의 핵심 12 | 중복 코드를 방지하고 설계를 더 간결하게 만들어 유지 보수성을 향상시킨다! 13 | 즉, 인자에 따라 설정이 다른 여러 부생성자가 있다고 가정했을 때 그들이 가지는 공통 검증 로직을 주 생성자에서 처리해줄 수 있습니다! 14 | 15 | ```java 16 | public Lotto { 17 | private int number; 18 | 19 | public Lotto(int number) { 20 | if (number < 0) { // 양수가 아닌 로또 숫자에 대한 검증 로직 21 | throw new InvalidLottoNumberException(); 22 | } 23 | this.number = number; 24 | } 25 | 26 | public Lotto(String number) { 27 | this(Lotto.parse(number)); // int로 변환 28 | } 29 | 30 | public Lotto(float number) { 31 | this((int)number); 32 | } 33 | } 34 | ``` 35 | 36 | 핵심은 내부 프로퍼티는 오직 한 곳(주생성자)에서만 초기화 해야하고 다른 모든 위치(부 생성자)에서는 단순히 인자를 준비(포맷팅, 파싱, 변환)만 해야합니다. 37 | 38 | # 느낀점 39 | 40 | -er에서 대해서는 완벽히 이해하기 어려웠다. 41 | 예를 들어 LottoNumberGenerator라는 친구가 있다고 했을 때, 42 | 여기서 말하는 것처럼 이 친구를 LottoNumber 안에서 make()와 같은 식으로 만든다면? 43 | 44 | 물론 LottoNumber가 예고르씨가 말한대로 절차적인 로직을 제거할 순 있겠지만, 45 | LottoNumber에 굉장히 많은 코드들이 들어가게 되서 되려 코드의 복잡성이 증가할 수 있다고 생각했다. 🤔 46 | 애매하구만,,, -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/cu.md: -------------------------------------------------------------------------------- 1 | # 2.9 인터페이스를 짧게 유지하고 smart를 사용하세요 2 | - 많은 생각이 들게 하는 페이지다. 도대체 Smart 객체 이 녀석의 역할은 무엇인가 Exchange 인터페이스 구현체는 어떤 역할을 하는가 Exchange 구현체와 달리 Smart 객체가 갖는 이점은 무엇인가, toUsd(), eurToUsd() 를 가진 객체를 생성하고 조합으로 해결할 수도 있지 않은가 3 | 4 | ```sh 5 | # What the.. 6 | public interface Exchange { 7 | float rate(String source, String target); 8 | final class Smart { 9 | private final Exchange origin; 10 | 11 | public Smart(Exchange origin) { 12 | this.origin = origin; 13 | } 14 | 15 | public float toUsd(String source) { 16 | return this.origin.rate(source, "USD"); 17 | 18 | } 19 | 20 | public float eurToUsd() { 21 | return this.toUsd("EUR"); 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | ```sh 28 | # 객체를 도출하면 되는 거 아닌가. 구현체에 반드시 Smart 객체의 기능이 필요하면(Is-a) 추상 클래스를 활용하던가 29 | new Cash(NYSE(), 100); 30 | 31 | public class UsdExchange implements Exchange { 32 | private final Exchange exchange; 33 | 34 | public UsdExchange(Exchange exchange) { 35 | this.exchange = exchange; 36 | } 37 | 38 | public static Exchange NYSE() { 39 | return new UsdExchange(new NYSE()); 40 | } 41 | 42 | @Override 43 | public float rate(String source, String target) { 44 | return 0; 45 | } 46 | 47 | public float toUsd(String source) { 48 | return this.exchange.rate(source, "USD"); 49 | 50 | } 51 | 52 | public float eurToUsd() { 53 | return this.toUsd("EUR"); 54 | } 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 2.9 인터페이스를 짧게 사용하고, smart를 사용하라. 2 | 3 | 예고르씨는 앞에서부터 계속 클래스를 작게 만들라고 했다.
4 | 그의 주장에 따르면 다수의 인터페이스를 구현한 클래스는 메서드가 많아질 수 있기 때문에 인터페이스 또한 작게 유지하라는 것이다.
5 | 6 | ```java 7 | interface Exchange { 8 | float rate(String target); 9 | float rate(String source, String target); 10 | } 11 | ``` 12 | 13 | 인터페이스는 구현 클래스가 준수해야할 계약인데, 위와 같은 인터페이스는 구현자에게 많은 것을 요구한다.
14 | 즉, source 값이 디폴트로 구현되는 rate(String target)은 포함되어서는 안된다는 것이다.
15 | 여기서 예고르는 smart를 사용하라고 말한다.
16 | 17 | ```java 18 | interface Exchange { 19 | float rate(String source, String target); 20 | final class Smart { 21 | private fianl Exchange origin; 22 | 23 | public float toUsd(String source) { 24 | return this.origin.rate(source, "USD"); 25 | } 26 | 27 | public float eurToUsd() { 28 | return this.toUsd("EUR"); 29 | } 30 | } 31 | } 32 | 33 | 34 | float rate = new Exchange.Smart(new NYSE()).eurToUsd(); 35 | ``` 36 | 37 | 솔직히 이렇게 써서 얻는 이점을 크게 못 느끼겠다.
38 | 그리고 가독성이 떨어진다. 일단 당장의 예시 코드만 봐도 바로 이해하기에는 어렵다고 느꼈다.
39 | 40 |
41 | 42 | 그리고 개인적인 인터페이스의 이점 중 하나는 구현된 코드를 숨기고 행동 규약만 보여준다는 점이었는데,
43 | 이렇게 되면 인터페이스를 통해 공통 코드가 그대로 노출되면서 인터페이스의 이점이 사라진다고 생각했다.
44 | 45 |
46 | 47 | 이뿐만이 아니라 사용하고 싶은 공통 기능에 따라 추상 클래스를 만들고 입맛에 맞게 하위 클래스가 상속받을 수 있을 텐데,
48 | 저렇게 인터페이스 내부에서 지정해버리면 하위의 구현 클래스들 모두 영향을 미칠 수 있겠다는 생각이 든다.
49 | 50 |
51 | 52 | 지금 내 생각으로는 이럴거면 쓰지 말라고 하지, 왜 짧게 사용하라는 건지 의문이다.
53 | 인터페이스가 어떤 이점을 가지고 사용되는 지 다시 생각해보면서 고민해봐야겠다.
-------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/wedge.md: -------------------------------------------------------------------------------- 1 | # 생성자엔 할당 이외에 다른 코드는 없어야 한다. 2 | - 필요한 경우에 인자를 다른 타입의 객체로 감싸거나 row-from으로 캡슐화하여 사용하여야 한다. 3 | 4 | - 다음은 Number를 프로퍼티로 가지는 Cash 객체에 String을 통해 생성하려고 할 때의 예제이다. parseInt를 통해 파싱하여 바로 할당하고 싶겠지만 다음 방식처럼 실행코드는 lazy하게 만들어야한다. 5 | ```java 6 | class Cash{ 7 | private Number dollars; 8 | 9 | Cash(String dlr){ 10 | this(new StringAsInteger(dlr)); 11 | } 12 | Cash(Number dlr){ 13 | this.dollars = dlr; 14 | } 15 | 16 | } 17 | 18 | class StringAsInteger implements Number{ 19 | private String source; 20 | 21 | StringAsInteger(String src){ 22 | this.source = src; 23 | } 24 | 25 | int intValue(){ 26 | return Integer.parseInt(this.source); 27 | } 28 | } 29 | ``` 30 | 31 | - 언뜻 생각하기에 생성자에서 한번만 해줬으면 될 parse를 여러번 하게 될테니, 성능상 문제가 생길 것 같지만 저자의 생각은 다르다. 32 | - build만 해주고 파싱 등의 로직은 **요청이 있을 때 (on demand)** 해줌으로써, 되려 성능 최적화를 할 수 있다. 33 | 34 | # 느낀 점 35 | - 성능이란건, 시점이 중요한 것 같다. 이 글을 읽기전까지의 관점은 `모든 게 완벽한 상황에서 행위가 몇번 일어나는가` 로 성능을 가늠했다. 36 | - (물리학에선 가정을 할 때 `진공 상태에 있는 완벽한 구형의 물체`를 많이 활용한다고 한다 ㅋㅋㅋ 하지만 실제세계에 그런 케이스는 존재하지 않는다. 비슷한 느낌인듯?) 37 | - 하지만 만약 해당 객체가 실제론 활용되지 않는다면 그런 최적화가 무슨 소용일까? 성능 최적화의 여지가 없다는 저자의 말에 동의한다. 38 | - 또, 한번만 했어도 될 파싱이 여러 번 일어났다고 해도, 해당 행위가 일어난 시점이 언제인가? 도 중요하다고 느꼈다. 39 | 한번 파싱의 비용이 1, 여러번 파싱의 비용이 5라고 가정해보자. 40 | 다른 객체들이 활발하게 활동하여 프로세서가 현재 처리해야 할 작업량이 99라고 한다면 이 객체가 `한번만` 파싱해서 발생하는 1의 작업량도 부담일 것이다. 41 | 이 객체만 활동하여 프로세서가 널널한(null null한 아님) 상황이라면 `여러번` 파싱의 비용도 부담이 아닐 것이다. 42 | 즉 행위가 발생하는 시점 또한 성능에서 중요한 요소 일거라는 깨달음.. 43 | 44 | - 그러나 시점까지 고려하여 성능 최적화를 한다는건 프로그램의 복잡성을 무시하는 처사일 것이다. 모든 상황을 예측할 수는 없다. 그러므로 무조건 최적화를 시켜놔야겠지만.. 시점도 생각해볼만한 주제라고 생각한다. 45 | -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 2.4 메서드 이름은 신중하게 선택하라. 2 | 3 | - 빌더 : 뭔가를 만들고 새로운 객체를 반환하는 메서드 👉 명사로 짓는다.
4 | - 조정자 : 객체로 추상화한 실세계 엔티티를 수정하는 메서드 👉 동사로 짓는다.
5 | 6 |
7 | 8 | 뭔가를 만드는 동시에 조작하는 메서드가 있어서는 안된다.
9 | 10 |
11 | 12 | 13 | ### 빌더는 명사다. 14 | '무언가'를 만드는 것을 요구할 때 원하는 '무언가'를 요청하기만 해야한다.
15 | (어떻게 만드는 지는 말해선 안된다.)
16 | 17 |
18 | 19 | `cookBrownie()`라는 메서드를 예를 들자면, 브라우니를 요구할 수는 있지만 만들어오는 건지 외부에서 사오는 건지는 요구할 수 없다.
20 | 이렇게 되면 `cookBrownie()` 메서드를 가지고 있는 `Bakery` 클래스를 자율적인 객체로써 존중하지 못하는 것이다.
21 | 22 |
23 | 24 | 객체는 자신의 의무를 수행하는 방법을 알고 있고 존중받기를 원하는 살아있는 유기체이다.
25 | 메서드 이름을 동사로 지을 때 = '무엇을 할 지' 알려주는 것
26 | 27 |
28 | 29 | ```java 30 | int add(int x, int y); // 더하라고 요청한다. (X) 31 | int sum(int x, int y); // 더한 값을 달라고 요청한다. (O) 32 | ``` 33 | 즉, 객체가 어떤일을 해야하는 지 직접적으로 이야기 하지 않는다.
34 | 35 |
36 |
37 | 38 | ### 조정자는 동사다. 39 | 객체를 뭔가 조정해야 한다면 이름은 동사고 반환값은 없다.
40 | 예시 : 바텐더에게 "음악을 틀어주세요"라고 말할 순 있지만 "음악을 틀고, 볼륨 상태를 말해주세요"라고 하는 것은 무례하다.
41 | 42 |
43 | 44 | ### 빌더와 조정자 혼합 45 | 46 | ```java 47 | class Document { 48 | OutputPipe output(); // [빌더]결과값을 요구한다.(OutputPipe를 반환) 49 | } 50 | 51 | class OutputPipe { 52 | void write(InputStream content); // [조정자]쓰기를 요구한다. (반환값 없음) 53 | int bytes(); 54 | long time(); 55 | } 56 | ``` 57 | 58 | ### Boolean을 반환하는 메서드 59 | 예외적인 경우로, 값을 반환하는 빌더이지만 가독성 측면에서 형용사로 지어야한다.
60 | 61 |
62 | 63 | # 느낀점 64 | 65 | 빌더와 조정자라는 개념이 딱 잡혀있는 것은 좋다고 생각된다.
66 | 하지만 빌더와 조정자의 혼합의 경우 저렇게 까지 나눌 필요가 있었나?라는 생각이다.
67 | 경험상 타고타고 들어가서 봐야되는 코드는 가독성이 많이 떨어진다고 느꼈다.
68 | 이론적으로는 동의할지언정, 실용적인 측면에서 동의하기 어렵다.
-------------------------------------------------------------------------------- /2장-학습/7절-문서를-작성하는-대신-테스트를-만드세요/gump.md: -------------------------------------------------------------------------------- 1 | # 2.7-문서를-작성하는-대신-테스트를-만드세요 2 | 3 | ## **정리** 4 | 5 | ### 읽기 쉬운 코드를 만들기 위해서는, 코드를 읽게 될 사람이 비즈니스 도메인, 프로그래밍 언어, 디자인 패턴, 알고리즘을 거의 이해하지 못하는 주니어 프로그래머라고 가정해야해요. 6 | 7 | 코드를 읽을 사람이 여러분보다 훨씬 더 멍청하다고 가정해야 해요. 이렇게 가정하는 편이 오히려 여러분들의 동료를 존중하는 일이에요. 8 | 9 | ### 나쁜 프로그래머는 복잡한 코드를 짜요, 훌륭한 프로그래머는 단순한 코드를 짜요. 10 | 11 | 이상적인 코드는 스스로를 설명하기 때문에 어떤 추가 문서도 필요하지 않아요 12 | 13 | ```java 14 | Employee jeff = department.employee("Jeff"); 15 | jeff.giveRaise(new Cash("$5,000")); 16 | if(jeff.perfomance() < 3.5){ 17 | jeff.fire(); 18 | } 19 | ``` 20 | 21 | ### 코드를 문서화하는 대신 코드를 깔끔하게(Clean) 만들어요. 22 | 23 | 단위 테스트 역시 클래스의 일부에요. 24 | 25 | 단위 테스트는 클래스의 일부이지 독릭적인 객체(Entity)가 아니에요 26 | 27 | ### 깔끔하고 유지보수 가능한 단위 테스트를 추가하면, 클래스를 더 깔끔하게 만들 수 있고 유지보수성을 향상시킬 수 있어요 28 | 29 | 한 장의 그림이 수천 단어 만큼의 가치가 있는 것처럼, 하나의 단위 테스트는 한 페이지 분량의 문서 만큼이나 가치가 있어요. 30 | 31 | 단위 테스트는 클래스의 사용 방법을 **보여주는데** 반해, 문서는 이해하고 해석하기 어려운 이야기를 들려줘요. 32 | 33 | ### 프로덕션 코드만큼 단위 테스트에도 관심을 기울여요 34 | 35 | ### 좋은 예제 36 | 37 | ```java 38 | class CashTest { 39 | @Test 40 | public void summarizes() { 41 | assertThat( 42 | new Cash("$5").plus(new Cash("$3")), 43 | equalTo(new Cash("$8")) 44 | ); 45 | } 46 | @Test 47 | public void deducts() { 48 | assertThat( 49 | new Cash("$7").plus(new Cash("-$11")), 50 | equalTo(new Cash("-$4")) 51 | ); 52 | } 53 | @Test 54 | public void multiplies() { 55 | assertThat( 56 | new Cash("$2").mul(3), 57 | equalTo(new Cash("$6")) 58 | ); 59 | } 60 | } 61 | ``` 62 | 63 | ## **의견** -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/wannte.md: -------------------------------------------------------------------------------- 1 | # 1.3 생성자에 코드를 넣지 마세요 2 | ## 내용 정리 3 | ### Don't touch the argument 4 | ```java 5 | class Cash { 6 | private int dollars; 7 | Cash(String dlr) { 8 | this.dollars = Integer.parseInt(dlr); 9 | } 10 | } 11 | ``` 12 | -> code-free 해야 함. 다른 타입의 객체로 감싸거나, Raw form 으로 캡슐화해야 함. 13 | ### 사용하는 시점까지 객체의 변환 작업을 연기한다. 14 | 객체 생성시 parsing vs 사용시 parsing 15 | 사용시 parsing 했을 경우 cache를 이용하여 최적화가 가능. 16 | 17 | | 객체를 인스턴스화하는 동안에는 객체를 build하는 일 이외에는 어떤 일도 수행하지 않습니다. 18 | 19 | ## 느낀 점 20 | 생성자에서의 유효성 검사에 관해 고민을 했었다. [생성자에서의 유효성 검사에 관하여 블로그 정리](https://velog.io/@wannte/%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EC%8B%9C-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%82%AC%EC%97%90-%EA%B4%80%ED%95%98%EC%97%AC) 21 | 22 | 글을 써가면서, primitive value를 캡슐화시켜 사용하였는데, 이는 단순히 그 책임만을 분리한 것이고, 객체스러운 구현은 아니었다. 23 | 24 | 25 | > 생각을 정리해보면, 객체를 단순히 저장의 수단 정도로 생각했던 것 같아요. 그 저장의 책임만을 분리해서 이를 객체라 정의하고 사용했던 것 같아요! 객체 내부의 구현을 외부에 숨긴다라는 말에 의문을 가지고 있었어요. 저장된 방식은 정해져 있고, 이를 외부에서 사용하는데 있어서, 숨김으로써 가지는 장점이 무엇이 있을까? 단순히 사용하기 편해서? 26 | 이번 단원을 읽으면서, 이 부분에서 가려운 부분을 긁어준 느낌이에요. 생성자의 역할을 줄이고, 내부 구현이 숨겨진 메소드에서 이 부분을 다른 방식으로 정리하여 사용하는 방식으로요.(parseInt를 메소드에서 처리하는 방법) 27 | 28 | 29 | 생성할 때의 인수를 그대로 가지고 있으면서도, 그 메소드의 구현 방식을 수정함으로써 더 객체스럽게 사용할 수 있다. 30 | 31 | 32 | ## ? 33 | ```java 34 | class StringAsInteger implements Number { 35 | private Sting text; 36 | StringAsInteger(String txt) { 37 | this.text = txt; 38 | } 39 | 40 | public int intValue() { 41 | return Integer.parseInt(this.text); 42 | } 43 | } 44 | ``` 45 | 로또 미션을 기억해보자. 돈을 입력받을 때 양의 정수만을 허용하고자 한다. 46 | 그렇다면, 지금의 구현은 객체를 생성한 이후에, validation 과정을 추가적으로 진행하여 검증 과정을 거쳐야 한다. 47 | 생성자와 검증 메소드가 강하게 결합하게 되서, 이와 같은 경우에서는 생성장에서 진행한느 것이 바람직 하지 싶다. 48 | -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/danyee.md: -------------------------------------------------------------------------------- 1 | # 1.3 생성자에 코드를 넣지 마세요 2 | 3 | ## 정리 4 | - 객체 초기화에는 코드가 없어야(code-free)하고 인자를 건드려서는 안 된다. 5 | - 대신, 필요하다면 인자들을 다른 타입의 객체로 감싸거나 가공하지 않은 형식(raw form)으로 캡슐화해야 한다. 6 |
7 | 8 | - 객체를 초기화하는 시점에 곧장 텍스트를 숫자로 변환하지 말고, 실제로 사용하는 시점까지 객체의 변환 작업을 연기해야 한다. 9 | 10 | ``` 11 | class Cash { 12 | private Number dollars; 13 | 14 | Cash (String dlr) { 15 | this.dollars = new StringAsInteger(dlr); 16 | } 17 | } 18 | 19 | class StringAsInteger implements Number { 20 | private String source; 21 | 22 | StringAsInteger(String src) { 23 | this.source = src; 24 | } 25 | 26 | int intValue() { 27 | return Integer.parseInt(this.source); 28 | } 29 | } 30 | ``` 31 |
32 | 33 | - 파싱이 여러 번 수행되지 않도록 하고 싶다면 데코레이터(decorator)를 추가해서 최초의 파싱 결과를 캐싱하면 된다. 34 | 35 | ``` 36 | class CachedNumber implements Number { 37 | private Number origin; 38 | private Collection cached = new ArrayList<>(1); 39 | 40 | public CachedNumber(Number num) { 41 | this.origin = num; 42 | } 43 | 44 | public int intValue() { 45 | if (this.cached.isEmpty()) { 46 | this.cached.add(this.origin.intValue()); 47 | } 48 | return this.cached.get(0); 49 | } 50 | } 51 | ``` 52 |
53 | 54 | - 생성자에서 코드를 없애면 사용자가 쉽게 제어할 수 있는 투명한 객체를 만들 수 있다. 55 | - 또한, 객체를 이해하고 사용하기 쉬워진다. 56 | - 결국, 객체는 요청을 받을 때만 행동하고 그 전에는 어떤 일도 하지 않는다. 👉 좋은 방식으로 매우 게으르다(lazy). 57 | 58 | ## 느낀점 59 | 60 | - 해당 내용을 읽으면서 프리코스 때 생성자에 코드를 넣었던 기억이 떠올랐다. 61 | - 저자가 사용한 예제처럼 `Integer.parseInt()` 사용했던 거 같은데,, 😂 62 | - 새삼 그때는 코딩 정말 아무것도 모르고 했구나 싶다. 63 | - 생성자에 코드를 넣지 않았을 때의 이점이 생각보다 큰 것 같다. 64 | - 데코레이터로 최초의 파싱 결과를 캐싱할 수 있다는 걸 처음 알았다. 다음에 미션하면서 활용해봐야겠다! -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/nabom.md: -------------------------------------------------------------------------------- 1 | ```java 2 | class Cash { 3 | private final int dollars; 4 | public Cash mul(int factor) { 5 | return new Cash(this.dollars * factor); 6 | } 7 | } 8 | ``` 9 | 10 | 위와 같이 상태를 변경한다면 새로운 객체를 생성해서 반환하는 것을 불변 객체라고 한다. 그 반대, 즉, 상태만 변경되는 객체를 가변 객체라 한다. 가변객체로 만들게 되면 예상치 못한 결과를 발견할 수도 있다. (모르는 곳에서 값 변경이 일어날 경우) 그렇다면 불변객체를 사용하게 된다면 어떤 장점이 있을까? 11 | 12 | - **식별자 가변성 문제가 없다.** 13 | - **실패 원자성** 14 | 15 | 실패 원자성이란 완전하고 견고한 상태의 객체를 가지거나 아니면 실패하거나 둘 중 하나만 가능한 특성이다. 가변객체경우 상태를 변경하다 에러가 발생한다면 내부의 어떤 것은 변경이 되어 있고 어떤 것은 변경에 실패하게 된다. 실패 원자성이 보장되지 않는다는 것이다. 16 | 17 | - **시간적 결합을 제거할 수 있다.** 18 | 19 | 만일 가변객체를 사용하여 먼저 객체를 생성하고 필드를 채워나가는 방식으로 가게 되면 중간 실행 순서가 꼬일 수도 있다. 하지만 불변 객체라면 이러한 결합을 제거할 수 있다. 20 | 21 | - **부수효과 제거** 22 | 23 | 중간 실수를 하더라도 원래 객체에는 영향이 없기 때문에 부수적인 효과가 없다. 24 | 25 | - **NULL 참조 없애기** 26 | 27 | null을 이용한 코드는 항상 피해야한다. 그리고 최대한 null 값을 소유하지 않도록 노력해야 한다. 처음 만들 때 완벽한 객체를 만드는게 좋다. 28 | 29 | - **스레드 안전성** 30 | 31 | 웹, 빅 데이터 등으로 인해 멀티 스레드 환경에서의 안전성은 매우 중요하다. 만일 가변 객체라면 멀티 스레드 환경에서의 안전성을 보장할 수 없다. 32 | 33 | - **더 작고 더 단순한 객체** 34 | 35 | 불변 객체를 만드려 하면 처음부터 완벽한 객체를 만들려고 노력한다. 그러다보니 생성자에 인자를 받는데 인자 수가 많아진다면 무언가 잘못된 것을 알고 계속 분리하려고 노력할 것이다. 자연스레 더 작고 더 단순한 객체가 탄생할 것이다. 36 | 37 | 불변 객체로만 만드려고 하면 한계점도 있다. 예를 들어, 지연 로딩을 사용하기가 힘들다. 최근 객체를 불변으로 유지하면서도 지연 로딩을 구현할 수 있는 다양한 해결방법이 나온다. 38 | 39 | ### 느낀점 40 | 41 | 불변 객체는 버그가 생길 수 있는 원천을 아예 없애버리는 것 같다. 42 | 43 | 물론, 계속해서 생기는 객체 때문에 메모리 면에서는 손해가 있겠지만 캐싱과 같은 기술을 사용할 수 있으니! 그리고 GC를 믿자!! 44 | 45 | 하지만 아쉬운 건 역시 외부 라이브러리와의 호환성인 것 같다. 현재 자바 진영에서는 JPA 기술을 이용해 도메인 주도 개발이 가장 핫한 것 같다. 하지만 JPA특성 상(영속성 컨텍스트를 이용한 더티체킹) 엔티티를 가변 객체로 만들어야하는 것으로 알고 있다. 이러한 점 때문에 실제 서비스에서는 적용하기 힘든 규칙이지만 충분히 변화가 일어나야 한다고 생각한다! 46 | 47 | JPA를 사용할 때 엔티티를 불변객체로 사용해도 작동하게끔 컨트리뷰트를 하자!! 가즈아!!!! -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 2.6 불변 책체로 만드세요 2 | 3 | - 모든 객체는 불변성을 가지도록 설계될 수 있다. 4 | - 언어적 차원에서 불가능한 경우도 있다. 지연된 로딩을 구현할 때 그 문제가 발생한다. 5 | ```java 6 | class Page { 7 | private final String uri; 8 | private String html; 9 | 10 | Page(String address) { 11 | this.uri = address; 12 | this.html = null; 13 | } 14 | 15 | public String content() { 16 | if (this.html == null) { 17 | this.html = //get data from network. 18 | } 19 | return this.html; 20 | } 21 | } 22 | ``` 23 | - 예고르는 이런 기능을 언어적 차원에서 지원해줘야 한다고 생각한다. 24 | 25 | ## 불변성을 가지면 얻는 장점 26 | - 식별자 가변성 27 | - 가변객체를 사용하면 객체의 상태에 변화에 따른 객체가 가리키는 식별자를 제대로 판단하지 못할 수 있다. 28 | - `five := new Money(5)` 를 했다가 `five.setMoney(10)` 을 하면 식별자 불일치가 발생한다. 29 | - 이러한 식별자 불일치는 심각한 버그로 이어질 수 있다. 30 | 31 | - 실패 원자성 32 | - 객체가 완전히 견고하거나, 아니면 실패하거나 둘중 하나의 상태만 갖는다. 33 | - 불변 객체를 사용하면 별다른 노력 없이 원자성을 얻을 수 있기 때문. 34 | 35 | - 시간적 결합 36 | - 코드의 순서에 따라 결과값이 달라지는 상황을 방지할 수 있다. 37 | - 가변객체의 경우 set의 위치에 따라 결과값이 달라질 수 있다. 38 | - 하지만 불변 객체를 사용하면 이러한 이이 발생하지 않는다. 39 | - 이는 초기화와 인스턴스화를 분리할 수 없는 특성으로 인해 발생한다. 40 | 41 | - 부수효과 체거 42 | - 로직에서 객체의 상태를 변화시킴에 따라 발생하는 에러를 방지할 수 있다. 43 | - 시간적 결합과 비슷한 느낌. 44 | 45 | - Null 참조 없애기 46 | - 값의 설정을 강제하기 때문에 null참조를 없앨 수 있다. 47 | 48 | - 스레드 안전성 49 | - 불변클래스를 사용하면 상태가 변하지 않는다. 50 | - 따라서 여러 스레드에서 동시에 데이터에 접근하더라도 데이터 불일치가 발생하지 않는다. 51 | - 가변객체도 스레드 안정성을 보장할 수 있지만 구현이 어렵다. 52 | 53 | - 더 작고 더 단순한 객체 54 | - 불변객체를 크게 만드는 일을 불가능하다. (생성자를 통한 초기화만 가능하기 때문) 55 | - 작은 객체는 유지보수성을 향상시킨다. 56 | 57 | 58 | ## 내 의견 59 | 이 장에 불만을 가진 당신. 반성하세요. -------------------------------------------------------------------------------- /3장-취업/6절-부-ctor-밖에서는-new를-사용하지-마세요/suri.md: -------------------------------------------------------------------------------- 1 | # 부 ctor 밖에서는 new를 사용하지 마세요 2 | 3 | - 하드코딩 된 의존성 4 | - Cash 클래스는 Exchange 클래스에 직접 연결되어 있기 때문에 의존성을 끊기 위해서는 Cash 클래스의 내부 코드를 변경할 수 밖에 없다. 5 | 6 | ```Java 7 | class cash { 8 | private final inte dollars; 9 | 10 | public int euro() { 11 | return new Exchange().rate("USD", "EUR") * this.dollars; 12 | } 13 | } 14 | ``` 15 | 16 | 17 | 18 | - Cash의 테스트를 한다면? Exchange()가 서버의 통신하는 기능과 같은 일을 한다면? 19 | - Cash를 테스트 할 때 다른 Exchange를 쓰고 싶어..! 20 | 21 | ```java 22 | class cash { 23 | private final int dollars; 24 | 25 | Cash() { 26 | this(0); 27 | } 28 | 29 | Cash(int value) { 30 | this(value, new NYSE()); 31 | } 32 | 33 | Cash(int value, Exchange exch) { 34 | this.dollars = value; 35 | this.exhange = exch; 36 | } 37 | 38 | public int euro() { 39 | return this.exchange.rate("USD", "EUR") * this.dollars; 40 | } 41 | } 42 | ``` 43 | 44 | 45 | 46 | - 부 생성자를 제외한 어떤 곳에서도 new를 사용하지 마세요. 47 | 48 | ```java 49 | class Request { 50 | private final Socket socket; 51 | 52 | public Request(Socket socket) { 53 | this.socket = skt; 54 | } 55 | 56 | public Request next() { 57 | return new SimpleRequest(/* 소켓에서 데이터를 읽는다. */) 58 | } 59 | } 60 | ``` 61 | 62 | 63 | 64 | - next()에서 사용하는 new를 없애려면 65 | 66 | ```java 67 | class Requests { 68 | private final Socket socket; 69 | private final Mapping mapping; 70 | 71 | public Requests(Socket socket) { 72 | this(skt, new Mapping() { 73 | @Oberride 74 | public Request map(String data) { 75 | return new SimpleRequest(data); 76 | } 77 | }) 78 | } 79 | } 80 | ``` 81 | 82 | 83 | 84 | # 느낀점 85 | 86 | - interface를 통한 의존성 주입은 너무 멋진 것 같다!! 87 | - 유연성이 늘어나는게 보임 88 | 89 | -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 1.3 생성자에 코드를 넣지 마세요 2 | 3 | - 인자에 손대지 말라 4 | - 실제로 인자를 사용하는 순간까지 변환 작업을 연기하라 5 | - 진정한 객체지향에서 인스턴스화란 더 작은 객체들을 조합해서 더 큰 객체를 생성한다. 6 | 7 | ```java 8 | class Cash { 9 | private Number dollars; 10 | 11 | Cash(String dir) { 12 | this.dollars = new StringAsinteger(dlr); 13 | } 14 | 15 | } 16 | 17 | class StringAsInteger implements Number { 18 | private String source; 19 | 20 | StringAsInteger(String srg) { 21 | this.source = src; 22 | } 23 | 24 | int intValue() { 25 | return Integer.parseInt(this.source); 26 | } 27 | 28 | } 29 | ``` 30 | 31 | - 위와같은 소스를 작성해야 한다. 32 | - 위와같은 소스가 성능상의 이득을 볼 수 있다 33 | - 이때 성능상의 이득이란 단순히 생각하는 '그것'이 아니고, 게으른 변환을 통해 얻는 성능이다. 34 | - 이 부분에서 많은 사람들이 의아해 할 것 같다. 간단한 예시를 들자면, 만일 어떤 객체 A의 생성자의 초기화 부분에서 DB에서 데이터를 가지고 와야 하는 경우가 있다고 가정해보자. 35 | 그리고, 객체A가 엄청 많이 생성되고, 그 중에서 특정 필드가 true인것만 filter해서 어떤 작업을 수행한다 생각해 보자. 36 | 만일 게으르지 않았다면 수많은 객체 A가 모두 dbconnection을 했을꺼고 성능상 손해가 심했을 것 이다. 37 | 하지만 게을렀다면 filter된 객체들만 db connectino을 했을것이고, 이런경우 성능의 차이를 많이 느낄 수 있을것이라 생각한다. 38 | 39 | 40 | # 내 생각 41 | 42 | ## 빠른 실패를 지향해야 한다. 43 | 과거에는 실패의 지연을 덕목으로 생각해 왔다. 하지만 요즘 와서는 빠른 실패를 하고 그에대한 리팩토링을 수행하는게 페러다임이 됐다. 44 | 이런편이 유지보수성이나 디버깅에서 모두 뛰어나다. 하지만 위와같은 방법은 실행을 지연시킨다. 이는 빠른 실패를 방해하며 디버깅의 어려움으로 귀결될 수 있는 문제를 야기한다. 45 | 46 | ## Optional을 사용할 수 있다. 47 | 예고르는 이 방식의 타당성을 부여하기 위해 데코레이터를 이용한 캐싱 기능을 소개한다. 48 | 하지만 생각해보면 이는 Optional과 완벽히 같은 기능을 수행하고 있는것을 알 수 있다. 49 | 굳이 데코레이터를 써야하나? 공식 API가 제공하는 Optinal로 대체하자. 50 | 51 | 52 | 개인적으로는 정말 마음에 들지 않는다. 지연된 실행이 주는 성능상의 이득도 있지만, 일반적인 경우(Integer.parseInt와 같은)는 그리 큰 성능 이득을 못느낄 것 이다. 53 | 객체지향적 사고를 하는건 좋지만 이건 조금 선 넘지 않았나 하는 생각이 든다. 54 | 객체지향적이 아니라 단순히 "구현"의 관점으로 보면, 위에서 내가 제시한 db connection 예시와 같은 경우는 사용하지 좋을 것 같다. -------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/koda.md: -------------------------------------------------------------------------------- 1 | # 내용 2 | 클래스는 단순히 객체의 템플릿이 아니라 객체의 생성 주기를 관리하는 능동적인 팩토리이다. 어떠한 객체가 어느 시점에 생성되고 파괴되고 어디서 생성되어서 협력할 것인지를 클래스가 관리한다. 3 | 따라서 객체가 가장 중요한(객체를 중심으로 구현이 되는) 객체지향에서는 객체를 관리하고 객체의 "저장소" 역할을 하는 클래스는 매우 중요한 역할을 가진 것이다. 이렇게 중요한 클래스의 이름은 아주 신중하게 정해야 한다. 4 | 5 | **제목에서 -er로 끝나는 이름을 사용하지 말라고 하는 이유 : -er로 끝나는 이름은 무엇을 하고 있는지 (doing)과 기능(functionality)에 기반해서 이름을 지은 것이기 때문이다.** 6 | 7 | 위 포인트가 좋지 않은 이유는 다음과 같다. 8 | - -er로 이름을 지었을 때는 어떠한 것을 "대표" 하는 객체가 아니라 기능위주의 "연결 장치"와 같은 역할을 하는 것으로 보인다. 9 | - 그러한 객체는 능동적인 관리자, 존재감 있게 무엇가를 대표하는 제대로된 객체가 아니다. 10 | - 스스로 데이터를 저장하고 관리해서 일을 수행하는 객체가 아니라 정보를 받아서 어떠한 일을 수행하고 전달해주는 역할을 하게 된다. 11 | 12 | 그럼 어떻게 이름을 지어야 할까? 13 | - 무엇을 하는지가 아니라 무엇인지에 기반해야 한다. 14 | - ex. CashFormatter -> Cash 15 | - 그의 역량(capability)로 이름을 특징 지어야 한다. 16 | - 클래스의 객체가 어떠한 것을 캡슐화 해야 하는지 깊이 고민하고 해당에 어울리는 이름을 지어야 한다. 17 | 18 | # 의견 19 | - 객체지향에서 클래스가 많은 것은 괜찮다(?)고 합리화하며 무분별하게 클래스를 생성했던 과거를 반성하게 되었다. 20 | - 클래스는 객체의 저장소이며 어플리케이션에서 중요한 대상을 대표하는 것이기 때문에 "무엇을 캡슐화 할 것인지"를 깊이 고민하고 생성해야 한다. 21 | - 단순히 한 클래스의 코드가 길어서 일부를 떼어서 새로운 클래스를 생성에 붙여넣는 행동들이 -er이 붙는 클래스를 생성하게 한 것 같다. 22 | - 이 클래스가 무엇인지 정체성을 고민하면 더 능동적으로 협업하는 객체지향 코드를 작성할 수 있을 것 같다. 23 | 24 | # 의문 25 | 1. 책에 나온 다음 문장이 잘 이해가 되지 않았다. 위에서 말한 것과 상충하는 느낌.. 26 | > 다시 말해서, 객체는 그의 역량(capability)으로 특징지어져야 합니다. 제가 어떤 사람인지는 키, 몸무게, 피부색과 같은 속성(attribute)이 아니라, 제가 할 수 있는 일(what I can do)로 설명해야 합니다. p.21 27 | 2. 위에서 말한 것은 다시 말해서 `모든 클래스는 인스턴스 필드를 가져야 한다` 라고도 이야기 할 수 있을까? 28 | 3. `객체지향의 사실과 오해`에서 중요하게 이야기 하는 부분은 객체를 메세지 중심으로 설계하라는 것이다. 29 | > 협력에 참여하는 훌륭한 객체 시민을 양성하기 위한 가장 중요한 덕목은 상태가 아니라 행동에 초점을 맞추는 것이다. p. 65 30 | > 메세지가 아니라 데이터를 중심으로 객체를 설계하는 방식은 객체의 내부 구조를 객체 정의의 일부로 만들기 때문에 객체의 자율성을 저해한다. p. 156 31 | > 위 내용에서는 객체가 메세지 중심으로 어떠한 책임을 담당하는지를 핵심으로 객체를 설계해야 한다고 하는데, 그럼 -er이 붙는 클래스처럼 기능 중심의 클래스도 그에 따른 기능적인 책임이 있으니까 괜찮은게 아닐까 하는 고민이 된다. 32 | > (두 책을 너무 단편적으로 가져와서 보는 것 같아서 잘못 생각한 걸 수도 있다고 생각합니다ㅠㅠ) -------------------------------------------------------------------------------- /1장-출생/2절-생성자_하나를_주_생성자로_만드세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 1-2. 생성자 하나를 주 생성자로 만드세요 2 | 3 | ### 2 ~ 3개의 메서드와 5 ~ 10개의 constructor 를 가진 객체를 만들어라 4 | - 여기서 말하는 메서드는 public 메서드이다. private 메서드는 예외이다. 5 | 6 | ### 주 constructor 와 부 constructor 로 나누어서 호출하라. (하나의 주 생성자여 여러개의 부 생성자를 가져라) 7 | ```java 8 | class Cash { 9 | private int dollars; 10 | 11 | Cash(float dlr) { 12 | this((int) dlr); 13 | } 14 | 15 | Cash(String dlr) { 16 | this(Cash.parse(dlr)); 17 | } 18 | 19 | Cash(int dlr) { 20 | this.dollars; 21 | } 22 | } 23 | ``` 24 | 25 | - 위 코드에서 주 생성자는 `Cash(int dlr)` 시그니처를 가진 생성자이며, 부 생성자는 나머지 생성자들이다. 26 | 주 constructor 모든 부 constructor 뒤에 위치시킨다. -> 유지보수성을 위함 27 | 28 | - 이와 같은 코드를 작성하면, 인자의 검증과 같은 로직의 중복을 막을 수 있다(주 생성자에서 한번만 검증하면 된다.). 29 | - 메서드 오버로딩을 이용하면 content(File), contentInCharset(File, Charset)대신 content(File), content(File, Charset)으로 소스를 간결하게 변경 가능. 30 | 31 | ## 나의 생각 32 | 몇가지 생각을 제외하고는 완벽하게 동의한다. 33 | 34 | - 2~3개의 메서드와 5~10개의 constructor 를 가진 객체를 만들어라 35 | - 아주 좋은 practice라고 생각한다. 메서드의 크기를 작게 만든다는 것은, 객체가 처리할 수 있는 데이터(필드)가 작아진다는 이야기가 되며, 이는 응집도를 자연스럽게 올릴 수 있는 방법이 된다. 36 | 37 | - 주 controller 와 부 controller 로 나누어서 호출하라 38 | - 저자는 주 controller를 모든 부 controller 뒤에 위치시킨다. 이는 유지보수성과 관련이 있다고 말한다. 하지만 나의 생각은 좀 다르다. 39 | 유지보수성을 위해서라면 주 생성자가 가장 위에 있어야 한다고 생각한다. 저자의 생각은 잘 알 것 같다. 클린코드의 원칙에서 메서드는 "읽는 순서대로" 정의되어야 한다고 한다. 40 | 하지만 생성자는 다르게 생각해야 한다고 생각한다. 생성자는 객체가 생성되는 부분이며, 소스의 중복을 위해 모든 생성과 관련된 로직이 주 생성자에 몰려있을 가능성이 높다. 41 | 따라서 클래스를 처음 봤을때 "생성자"가 가장 처음에 보이는게 맞다고 생각하며 이 편이 유지보수성에 더 도움이 된다고 생각한다. 42 | 43 | - 저자는 메서드 오버로딩을 이용하면 간결한 표현이 가능하다고 말하였다. 하지만 이게 과연 맞을까? 일단 클린코드의 원칙에 따라서, 메서드의 인자는 2~3개를 넘지 않는것이 좋다. 44 | 이 가정이 지켜진다고 생각하면, 나는 메서드 오버로딩을 통해서 파라메터를 숨기는 것 보다 메서드의 이름을 통해서 파라메터와 의미를 드러내는편이 더 좋다고 생각한다. 45 | 이 내용은 클린코드에도 있다. 실제로, 클린코드를 읽고 오버로딩 보다는 메서드 이름에 모든덜 드러낸 코드를 작성했을 때, 가독성면에서 더 좋은 느낌을 얻었었다. 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/koda.md: -------------------------------------------------------------------------------- 1 | # 2.4 메서드 이름을 신중하게 선택하세요 2 | 3 | ## 내용 4 | 5 | - **빌더는 명사로 메서드 이름을 지어야한다.** 6 | 7 | - 빌더는 반환값이 있는 것이다. 8 | 9 | ```sql 10 | int pow(int base, int power); 11 | float speead(); 12 | ``` 13 | 14 | - 빌더를 동사로 짓는 것은 객체에게 명령형으로 선언하는 것이 아니라 구체적으로 지시하고 있는 것이기 때문에 객체를 존중하지 않는 것이다. 15 | 16 | - 빌더가 어떻게 작동하는지 상관없이 계약을 준수하는 결과를 내도록 요청하는 것이 옳다. 17 | 18 | - **조정자는 동사로 메서드 이름을 지어야한다.** 19 | 20 | - 조정자는 엔티티를 수정하는 메서드이다. 21 | 22 | ```sql 23 | void save(String content); 24 | void put(String key, flaot value); 25 | ``` 26 | 27 | - 작가는 빌더 패턴에 반대한다. 왜냐하면 빌터 패턴은 더 커다란 객체를 만들도록 조장하기 때문이다. 빌더 패턴이 많다는 것은 반환할 인자가 많다는 것이기 때문에 해당 클래스가 크다는 것을 의미한다. 따라서 빌더 패턴을 사용하기보다 더 작은 객체들로 나눈 것이 더 바람직하다. (이 부분은 잘 이해하지 못했다 ㅠㅠ) 28 | 29 | - 빌더와 조정자는 혼합될 수 없다. 30 | 31 | 예를 들어서 document를 쓰고 쓴 document의 byte수를 반환 하는 경우 다음과 같이 변경해야 한다. 32 | 33 | ```sql 34 | //변경 전 35 | class Document { 36 | int write(InputStream content); 37 | } 38 | 39 | //변경 후 40 | class Document { 41 | OutputPipe output(); 42 | } 43 | 44 | class OutputPipe { 45 | void write(InputStream content); 46 | int byte(); 47 | int time(); 48 | } 49 | ``` 50 | 51 | - Boolean 값을 사용하는 경우에는 이름을 형용사로 지어야 한다. 52 | 53 | ```sql 54 | boolean empty(); 55 | boolean readable(); 56 | ``` 57 | 58 | ## 의견 59 | 60 | - 이 개념을 포비의 수업 때 처음 접하고 이후에 이렇게 일관성 있게 메서드 이름을 정하려고 했었다. 그랬더니코드가 더 깔끔하게 느껴졌다. 특히 조정사와 빌더를 혼합한 메서드가 많았는데 이러한 경우 작가가 말한 것처럼 이 메서드에서 책임지고자 하는 부분이 무언가를 조정하는 것인지 무언가를 가져오는 것인지 모호한 경우가 많았다. 위 규칙으로 이름을 짓다보면 메서드가 책임지고 있는 것이 무엇인지 다시한번 고민하게 되어서 유용했다. 61 | 62 | ## 의문 63 | 64 | - 빌더 패턴을 사용하기 보다 더 작은 객체로 나누라는 부분이 잘 이해가 되지 않았다. 작은 객체로 나눈다면 상호 협력시 각 객체에서 빌더를 호출할 일이 더 많아지지 않을까? 65 | 66 | - 조정사와 빌더가 혼합되었을 때 작가의 예시에서 `OutputPipe` 로 해결한 것이 잘 와닿지 않았다. 67 | 68 | 보기에는 OutputPipe를 반환하고 해당 객체를 통해서 따로 write 하고 byte를 반환 받는 것이 하나로 처리할 수 있는 일에 대한 불필요한 절차를 늘리는 느낌이다. 절차는 없을수록 스스로나 다른 개발자에게 더 좋을 것 같은데 작가의 예시처럼 바꾸면 절차가 더 생겨서 의사소통 비용이 생기지 않을까? -------------------------------------------------------------------------------- /1장-출생/3절-생성자에_코드를_넣지_마세요/gump.md: -------------------------------------------------------------------------------- 1 | # 1.3 -생성자에 코드를 넣지 마세요 2 | 3 | ## **정리** 4 | 5 | ### **생성자에는 코드가 없어야(Code-Free)하고, 오직 할당문만 포함해야해요** 6 | 7 | ```java 8 | class StringAsInteger implements Number { 9 | private String text; 10 | 11 | public StringAsInteger(String text) { 12 | this.text = text; 13 | } 14 | 15 | public int intValue() { 16 | return Integer.parseInt(this.text); 17 | } 18 | } 19 | ``` 20 | 21 | 생성자에 코드가 없는 모습을 볼 수 있어요. intValue()를 호출 할 때 마다 파싱을 하니, 느린 것이 아닌가? 라는 의문점이 들 수 있어요. 22 | 23 | ```java 24 | class StringAsInteger implements Number { 25 | private int number; 26 | 27 | public StringAsInteger(String text) { 28 | this.number = Integer.parseInt(text); 29 | } 30 | 31 | public int intValue() { 32 | return this.number; 33 | } 34 | } 35 | ``` 36 | 37 | 위와 같은 코드는 텍스트 파싱은 객체를 초기화 하는 시점에 단 한번 수행하기 때문에 효율적으로 보이지만, 최적화가 불가능해요. 만들때 마다 파싱이 수행되기 때문에 실행 여부를 제어할 수 없기 때문이죠. 38 | 39 | ### **인자를 전달된 상태 그대로 캡슐화하고 나중에 요청이 있을 때 파싱하도록 하면, 파싱 시점을 자유롭게 결정할 수 있어요.** 40 | 41 | 파싱이 여러 번 수행되지 않도록 하고 싶다면 **데코레이터를(decorator)**를 추가해서 최초의 파싱을 캐싱할 수 있어요. 42 | 43 | ```java 44 | class CahcedNumber implements Number { 45 | private Number origin; 46 | private List cached = new ArrayList<>(); 47 | 48 | public CahcedNumber(Number number) { 49 | this.origin = number; 50 | } 51 | 52 | public int intValue() { 53 | if (this.cached.isEmpty()) { 54 | this.cached.add(this.origin.intValue()); 55 | } 56 | return this.cached.get(0); 57 | } 58 | } 59 | ``` 60 | 61 | ```java 62 | Number number = new CachedNumber(new StringAsInteger("123")); 63 | 64 | number.intValue(); //첫 번째 파식 65 | number.intValue(); // 여기에서는 파싱하지 않음 66 | ``` 67 | 68 | 객체를 인스턴스화하는 동안 객체를 만드는 일 이외에는 어떤 일도 수행하지 않아요. 69 | 70 | 생성자에 코드를 없에면 사용자가 쉽게 제어할 수 있는 투명한 객체를 만들 수 있으며, 객체를 이해하고 재사용하기도 쉬워져요. 71 | 72 | ## **의견** 73 | 74 | 생성자에 코드를 없애다 보면, 클래스가 많아지게 돼요. 클래스를 많이 만드는게 서비스에 좋을 듯 한거 같기도 하고, 아닌거 같기도 하고, 더 많은 코드를 짜봐야 알겠어요. -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 2.6 불변 객체로 만드세요 2 | 불변 클래스로 구현하면 크기가 작고, 응집력이 높으며, 유지보수하기 쉬운 클래스를 만들 수 있습니다.
3 | 4 |
5 | 6 | ### 장점1. 식별자 가변성 7 | 객체의 상태가 변경되었을 경우 두 객체는 동일한가?
8 | 예를 들어 객체 A와 객체 B가 Map에 담겨있다고 했을 때, 객체의 상태가 변경되어 객체 B와 상태가 같아진다면 어떻게 될까?
9 | 이러한 에러를 방지할 수 있는 것이 불변 객체이다.
10 | 11 |
12 | 13 | ### 장점2. 실패 원자성 14 | 완전하고 견고한 상태의 객체를 가지거나 아니면 실패하거나 둘 중 하나만 가능하다는 특성을 의미한다.
15 | 가변 객체의 경우 상태가 변경될 때 그 안에서 별도의 처리를 함으로써 실패 원자성을 보장할 수는 있다.
16 | 하지만 객체의 복잡성이 훨씬 더 높아지고, 실수할 가능성이 커지기 때문에 불변 객체로 사용하는 것이 이 특성을 지키기에 편리하다.
17 | 18 |
19 | 20 | ### 장점3. 시간적 결합 21 | 불변 객체를 이용하면 시간적 결합을 제거할 수 있다.
22 | 23 | ```java 24 | Cash price = new Cach(); 25 | // x를 계산하기 위한 로직들 26 | price.setDollars(x); 27 | // y를 계산하기 위한 로직들 28 | price.setCents(y); 29 | // 다른 일을 수행하기 위한 로직들 30 | System.out.println(price); 31 | ``` 32 | 33 | 위와 같이 시간적 결합이 생기게 되면 코드의 복잡도가 증가함에 따라 유지보수가 어려워진다.
34 | 35 |
36 | 37 | private 프로퍼티가 NULL값을 가지도록 골격을 만들고, setter를 통해 프로퍼티 값을 설정하여 초기화하는 것은 JPA, JavaBeans와 같은 다양한 표준에서 Java 객체 조작을 위해 권장되는 방식이다.
38 | 하지만 객체 사고의 관점에서는 잘못되었다고 말한다.
39 | 40 |
41 | 42 | 나는 예고르씨가 생성자에 로직을 넣지말라고 말할 때 들었던 예시가 위와 같은 흐름이라고 생각한다.
43 | 내가 이해를 잘못한 것이 아니라면 이 사람은 자기 논리에 발목이 잡힌 게 아닐가...
44 | 45 |
46 | 47 | ### 장점4. 부수효과 제거(side effect-free) 48 | 객체는 예측가능해야한다. 그래야 믿고 사용할 수 있다.
49 | 50 |
51 | 52 | ### 장점5. NULL 참조 없애기 53 | 구구절절 설명했지만 결국 객체가 불변한다면 애초에 NULL이라는 값 자체가 들어올 수가 없다는 말.
54 | 55 |
56 | 57 | ### 장점6. 스레드 안정성 58 | 객체가 여러 스레드에서 동시에 사용될 수 있으며 그 결과를 항상 예측할 수 있었야한다.
59 | synchronized 처리를 통해 스레드에 안전하게 만들 수는 있지만 쉽지 않고, (뭐가 쉽지 않다는 건디)
60 | 동기화 로직을 추가하는 일은 성능상의 비용을 초래한다. (각 스레드가 객체를 사용하기 위해 객체가 다른 스레드로부터 해방 될때까지 기다려야한다.)
61 | 62 |
63 | 64 | ### 장점7. 단순성 65 | 단순성 = 유지보수성
66 | 객체가 단순해질 수록 응집도는 더 높아지고 유지보수는 더 쉬워진다.
67 | 단순하다는 것은 더 적은 코드 줄 수를 의미한다.
68 |
69 | 클래스가 짧을 수록 하는 일이 무엇이고, 어디에서 실패하고, 어떻게 리팩토링해야하는 지를 더 쉽게 이해할 수 있다.
70 | 71 |
72 | 73 | 예고르씨가 불변객체를 사용하는 가장 큰 이유인 단순성에 대한 이야기를 듣고 있자니 그래서 그렇게 나노 단위로 클래스를 나눴구나 싶다.
-------------------------------------------------------------------------------- /3장-취업/2절-정적-메서드를-사용하지-마세요/pomo.md: -------------------------------------------------------------------------------- 1 | # 3.2 정적 메서드를 사용하지 마세요. 2 | ```java 3 | // 정적 메서드를 사용하는 경우 4 | class WebPage { 5 | public static String read(String url) { 6 | // body 7 | } 8 | } 9 | 10 | // 정적 메서드 제거하고 객체를 사용하기 11 | class WebPage { 12 | private final String uri; 13 | public String content() { 14 | // body 15 | } 16 | } 17 | ``` 18 | 19 | 예고르는 정적 메서드 사용을 하지 않고, 객체를 만들어 사용하라고 한다.
20 | 예고르씨가 말하는 정적 메서드의 단점들에 대해 알아보도록 하자.
21 | 22 |
23 | 24 | ### 1) 순차적인 사고 방식을 요구한다. 25 | ```java 26 | int maxValue = Math.max(3, 1); 27 | ``` 28 | 할 일을 지시하는 것이 아니라 정의해야한다. 현재 코드는 컴퓨터에게 최댓값을 계산하라고 요청한다.
29 | 30 |
31 | 32 | ### 2) 명령형 스타일 33 | 34 | 명령형 스타일은 메서드를 호출한 시점에 CPU가 즉시 결과를 계산합니다.
35 | 36 |
37 | 38 | ### 1-1) 선언형 방식은 더 빠르다. 39 | 40 | - 성능 최적화를 제어할 수 있다. 41 | - 오직 하나의 정적 메서드만 호출하는 경우 외에는 순차적으로 호출되어야하는 정적 메서드는 느리다. 42 | - CPU에게 모든 것을 계산하라고 하지 않고 결과가 필요한 시점과 위치를 결정하도록 위임하고, 요청이 있을 경우에만 계산을 실행합니다. 43 | 44 |
45 | 46 | #### 1-2) 다형성 47 | 48 | - 다형성이란 코드 블록 사이의 의존성을 끊을 수 있는 능력을 의미한다. 49 | - 정적 메서드가 아닌 클래스를 사용하게 되면 아주 쉽게 분리해낼 수 있다. 50 | - 단, 객체를 다른 객체로부터 분리하기 위해서는 메서드나 주 생성자 어디에서도 new 연산을 사용해서는 안된다. 51 | 52 |
53 | 54 | #### 1-3) 표현력 55 | 56 | - 명령형 방식에서 결과를 예상하기 위해 코드를 머릿속에서 실행해야하기 때문에, 명령형 방식이 선언형 방식보다 덜 직관적입니다. 57 | - 알고리즘과 실행 대신 객체와 행동의 관점에서 사고하기 시작하면 무엇이 올바른지 느낄 수 있다. 58 | 59 |
60 | 61 | #### 1-4) 응집도 62 | 63 | - 계산을 책임지는 모든 코드들은 한 곳에 뭉쳐있기에 실수로라도 분리할 일이 없다. 64 | - 실수로 코드의 순서를 쉽게 변경할 수 있으며 그로 인해 알고리즘에 오류가 발생할 가능성이 적다. 65 | 66 |
67 | 68 | ## 유틸리티 클래스 69 | 유틸리티 클래스는 '클래스'의 인스턴스가 생성되는 것을 방지하기 위해 private 생성자를 추가하는 것이 좋다.
70 | 여기서 재밌는 점은 유틸 클래스가 절차적인 프로그래머들이 OOP라는 영토에서 거둔 승리의 상징이라고 표현하는 것이다.
71 | 72 |
73 | 74 | ## 싱글톤 패턴 75 | Math 클래스는 싱글톤의 대표적인 예이다.
76 | 사실 Math 클래스도 유틸 클래스로 만들 수 있다. 그렇다면 왜 싱글톤을 나오게 되었을까?
77 | 그 이유는 싱글톤은 상태를 캡슐화할 수 있다는 점이다.
78 | 싱글톤은 분리 가능한 의존성으로 연결되어 있는 반면, 유틸 클래스는 분리 불가능한 하드 코딩된 결합도를 가진다는 것이다.
79 | 하지만 싱글톤 역시 전역변수 그 이상 이하도 아니라고 말한다.
80 | 싱글톤을 사용하지 말고 캡슐화에 집중하라!
81 | 82 |
83 | 84 | 아직 완벽하게 이해하기엔 무리이지만 정적 메서드 사용을 줄이는 것에는 동의한다.
-------------------------------------------------------------------------------- /1장-출생/1절-er로_끝나는_이름을_사용하지_마세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 1-1. -er로 끝나는 이름을 사용하지 마세요. 2 | 3 | ### 클래스는 객체의 팩토리다. 4 | - 클래스가 객체를 **인스턴스**화 한다. 5 | - 클래스는 객체를 만들고, 추적하고, 적절한 시점에 파괴한다. 6 | 7 | - 클래스는 객체의 템플릿이 아니다. 8 | - 객체의 템플릿으로 클래스를 취급하는 순간, 클래스는 단순한 코드 덩어리로 취급된다. 9 | 10 | ### 클래스의 이름은 **무엇을 하는지**가 아니라 **무엇인지**에 기반해야 한다. 11 | - 연결장치는 존중받지 못한다. 12 | - 객체는 살이있는 유기체이며, 캡슐화된 데이터를 나타내는 *대표자*이다. 13 | - 객체가 존중받기 위해서는 스스로 어떤 일울 수행할 만큼 강력하고 똑똑해야한다. 14 | - 멍청한 객체는 단순히 데이터를 전달하기만 한다. 15 | 16 | - 'er'을 사용하지 말 것 17 | - 'er'이라는 접미사는 데이터를 다루는 **절차**를 나타낸다. 절차지향 언어들의 산물일 뿐이다. 18 | 19 | ## 나의 생각 20 | 저자는 객체를 살아있는 유기체로 생각하고 있다. 21 | 또한 실제 객체지향 프로그래밍도 객체는 살아있는 유기체이며 이들의 협력으로 객체지향 세계를 구축한다고 정의한다. 22 | 23 | 엘레강트 오브젝트는 객체지향 프로그래밍에 대한 지식이 부족하면 저자가 말하고자 하는 바를 제대로 이해할 수 없는 책이라고 생각한다. 24 | 이번 장 또한, 객체지향의 기본 원칙. *객체는 살아있는 유기체이며 객체지향 세계는 이들의 협력으로 이루어진다*는 기본 원칙을 매우 충실히 지키기 위해 노력한다. 25 | 26 | - 클래스는 객체의 팩토리다. 27 | - 당연한 이야기다. 클래스를 단순히 객체의 템플릿(붕어빵 틀)으로 생각한다면, 그에 따라 생성되는 객체 또한 그정도 수준으로 밖에 평가되지 못한다. 28 | 객체는 살아있는 유기체라는 사실을 명심해야 한다. 객체가 생성되는것에는 그 이유가 존재하며 이유없이 생성되는 객체는 없다. 29 | 생성된 객체는 객체지향 세계에서 책임, 역할을 갖고 다른 객체와의 협력을 통해서 자신의 생명주기를 이어간다. 30 | 31 | 사실 클래스가 객체의 템플릿이라는 관점이 완전히 틀린것은 아니라고 생각한다. 단, 언어적 관점에서 봤을때만 그렇다. 하지만 이 책을 객체지향에 관하서 서술하고 있다. 32 | 따라서 클래스는 단순히 템플릿으로 생각되면 안된다. 책에서는 클래스를 객체의 "어머니" 라고 비유했지는 나의 생각은 다르다. 33 | 클래스는 객체들의 "조물자"이다. 그들의 탄생과 죽을을 관리하며, 그들의 특성 또한 모두 클래스를 통해 부여된다. 34 | 35 | - 클래스의 이름은 **무엇을 하는지**가 아니라 **무엇인지**에 기반해야 한다. 36 | - 정말 완벽하게 공감한다. 객체지향 세계에서 객체는 자기 자신을 나타내는 *대표자*이며 *살아있는 유기체*이다. 37 | 실제 생활에서도 생각해 보자, 각자에게는 이름이 있고, 그 이름을 불림으로써 존중받는다. 그들에게 "코딩하는사람", "달리는사람" 이라고 부르는 것은 그들을 존중하지 않는 행위이다. 38 | 39 | 객체또한 마찬가지이다. 객체의 이름은 객체가 "무엇인지" 나타내야지 "무엇을 하는지" 나타내어서는 안된다. 40 | 'er'이 붙는 클래스는, 단순히 그 행위를 하는, 데이터의 전달자일 뿐이라고 명시하는 것 이다. 이들은 유기체가 아니다. 단순히 그 행위를 하는 '무엇'일 뿐이다. 41 | 객체가 객체지향 세계에서 존중받기 위해서는 단순히 다른 객체에게 쓰여지고 버려지는 존재여서는 안된다. 42 | 43 | 이러한 관점으로 봤을 때 클래스의 이름은 객체가 "무엇인지" 나타내야 한다. 무엇을 하는지 나타내는 것은 가수를 "딴따라"라고 부르는 것과 다를 바 없다. 44 | 45 | 46 | 객체지향적 사고로 생각한다는 것이 쉽지많은 않다고 생각한다. 하지만 객체지향적으로 세상을 바라보고, 코드를 바라본다면, 예고르의 이 의견에 정말 공감할 수 밖에 없을것이라 생각한다. 47 | 48 | 49 | -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/ryan.md: -------------------------------------------------------------------------------- 1 | ## 메서드의 이름을 신중하게 선택하세요 2 | 3 | - 빌더(새로운객체반환)의 이름은 명사로, 조정자(수정하는메서드)의 이름은 동사로 4 | - 형용사를 추가해서 빌더의 이름이 나타내는바를 더 명확히 5 | - 부사를 추가해서 조정자의 이름이 나타내는바를 더 명확히 6 | 7 | 8 | 9 | #### 2.4.1 빌더는 명사다 10 | 11 | 필자의 주장 : 잘못된 예시 12 | 13 | ```java 14 | class Bakery{ 15 | Food cookBrownie(); 16 | Drink brewCupOfCoffee(String flavor); 17 | } 18 | ``` 19 | 20 | 실 사용에서는 `Food something = parisBakery.cookBrownie()` 21 | 22 | 이거는 이상한 느낌이 있긴 하다. 해석하면 빠바 브라우니 요리하다. 23 | 24 | 25 | 26 | 27 | 28 | 필자가 주장: 좋은 예시 29 | 30 | ```java 31 | class Bakery{ 32 | Food cookedBrownie(); 33 | Drink brewedCupOfCoffee(String flavor); 34 | } 35 | ``` 36 | 37 | 실 사용에서는 `Food something = parisBakery.cookedBrownie()` 38 | 39 | 내가 보긴 이거도 이상하다. 해석하면 빠바 요리된 브라우니. 40 | 41 | 42 | 43 | 마이 아이디어: 44 | 45 | `Food something = parisBakery.getCookedBrownie()` 46 | 47 | 내 생각엔 이게 더 자연스러운데? 빠바에서 요리된 브라우니를 받다. 48 | 49 | 내 생각엔 예고르형님 아이디어 + 게터를 추가시키면 더 자연스러운 문장이 탄생함.(게터+(형용사)+명사형) 50 | 51 | 52 | 53 | #### 2.4.2 조정자는 동사다 54 | 55 | 조정자는 동사를 써라 56 | 57 | 빌더패턴도 쓰지말라함. 58 | 59 | 60 | 61 | #### 2.4.3 빌더와 조정자 혼합하기 62 | 63 | 혼합해서 사용하지 마라 64 | 65 | 66 | 67 | #### 2.4.4 Boolean 값을 결과로 반환하는 경우 68 | 69 | `isExists()` 는 잘못됬고 `exists()` 가 맞다 70 | 71 | `isEmpty()` 는 잘못됬고 `empty()`가 맞다 72 | 73 | 라고 주장한다. 74 | 75 | 내 생각에도 확실히 동사+동사 조합은 이상하다. 76 | 77 | 하지만 동사+형용사 조합은 자연스럽다고 생각한다. 78 | 79 | `yourName.empty();` 80 | 81 | 그래ㅏ, 뭐 이상하지는 않은데... 82 | 83 | `yourName.isEmpty();` 84 | 85 | 이게 더 낫지 않나? 86 | 87 | `your name is empty?` 88 | 89 | `your name empty?` 90 | 91 | 음.... 92 | 93 | 원래 영어에서도 94 | 95 | 뒷부분 발음만 살짝 올려 말하면 의문문이 된다. 96 | 97 | 이것도 그렇게 생각햇을때 is 가 잇는것이 더 읽기 좋음. 98 | 99 | 100 | 101 | 102 | 103 | `if(name.emptiness() == true)` 104 | 105 | 이것을 `if emptiness **of the ** name is true `이렇게 읽으면 자연스럽다는데.. 106 | 107 | `if(name.isEmpty() == true)` 108 | 109 | 나는 `if yourname is empty? True` 이게 더 자연스럽게 읽힌다. 110 | 111 | 112 | 113 | of the 를 무의식적에 붙여 읽는거는 좀 아니자나~~~ 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/cu.md: -------------------------------------------------------------------------------- 1 | # 2.8 모의 객체(Mock) 대신 페이크 객치(Fake)를 사용하세요 2 | 3 | - Fake 객체를 사용하면 Mock 객체를 사용하는 것보다 가독성에서 이점이 있다. 4 | - Mock 객체는 가정을 사실로 전환시키기 때문에 단위 테스트를 유지보수하기 어렵다. 5 | Mock 객체는 특정 메서드를 호출하리라고 "가정"하는데, 이는 객체의 구현방법을 알고 있다고 가정하는 것이다. 6 | 그러다보니 단위테스트하려는 대상의 행동이 변하지 않았는데도, 가정했던 도메인의 변화로 테스트코드가 깨질 수 있다. 7 | 단위 테스트를 클래스의 일부로 보는 시각에서 이는 OCP 원칙 위반으로 캡슐화가 깨졌음을 의미한다. 8 | - 협력 객체의 변화로 단위테스트를 수정해야 한다면 리팩토링 비용이 증가한다. 9 | - 인터페이스 설계를 더 깊이 고민하도록 만든다. 10 | 11 | ### 느낀 점 12 | 13 | - 끝나지 않을 classicist vs mockist : classicist 는 상태 검증이 목적이고, mockist는 행위 검증이 목적이다. 14 | 15 | #### * classicist 16 | - 저자의 경우 객체의 자율을 존중하는 쪽이라 classicist인듯 하다. classicist는 가능한 실제 객체를 사용하고 객체간의 협력으로 복잡한 케이스일 경우, 테스트 더블을 사용한다. 17 | 18 | - 테스트 더블이란, 테스트 목적으로 실제 객체 대신 사용되는 모든 종류의 가짜 객체를 의미한다. 테스트 더블에는 stub, mock, fake , spy 등이 있다. classicist 는 이 중 stub 혹은 fake 객체를 사용한다. 19 | 20 | > https://martinfowler.com/articles/mocksArentStubs.html 21 | 22 | - fake 와 stub은 다시 봐도 헷갈린다. fake 객체는 실제로 Production에는 사용하지 않고 간단한 기능만을 제공하는 객체를 의미하고, stub이란 미리 지정된 답변으로 응답하는 것을 의미한다고 설명이 되어 있다. 그럼 생각해보면, 아래와 같아야 할텐데 예제코드는 반대로다. 왜 MailServiceStub인지 설명 좀.. 23 | 24 | ```sh 25 | public class MenuFake implements Menu { 26 | private static final List menus = new ArrayList<>(); 27 | 28 | static { 29 | menus.add(new Menu(1, "후라이드", Category.CHICKEN, 16_000)); 30 | menus.add(new Menu(2, "양념치킨", Category.CHICKEN, 16_000)); 31 | } 32 | 33 | public static List menus() { 34 | return Collections.unmodifiableList(menus); 35 | } 36 | } 37 | ``` 38 | 39 | ```sh 40 | @Override 41 | float rate(String origin, String target) { 42 | return 1.2345; 43 | } 44 | ``` 45 | 46 | #### * mockist 47 | - mockist(bdd(given-when-then)도 mockist의 분파)는 mock에 대한 측을 명세하고 테스트를 시작한다. 협력객체들의 행동을 예측해두어 계층적(layered)인 구조에서도 잘 동작한다. 객체에 메시지를 던지고 그 응답만을 테스트하고 인접한 Mock들을 작성하므로 복잡한 픽스쳐를 구성할 필요도 없고, 테스트를 고립시킬 수 있다. 48 | - 지금껏 일하면서 테스트 시나리오를 작성하기 용이해서 given-when-then 패턴을 활용해왔고 행위 검증의 테스트가 의도를 분명히 드러낸다고 생각해왔다. 하지만, stubbing을 할 경우, 협력하는 객체의 Public Operation이 변경할 경우 바로 감지가 가능하나, mockist의 방식은 감지하기 어려울 수 있다. 테스트에서의 예측이 잘못되었는데도 단위테스트 결과가 성공이라 버그를 내포할 수 있다. 49 | -------------------------------------------------------------------------------- /2장-학습/6절-불변-객체로-만드세요/joanne.md: -------------------------------------------------------------------------------- 1 | # 2.6 불변 객체로 만드세요. 2 | 3 | # Contents 4 | 5 | 불변 객체는 필요한 모든 것을 내부에 캡슐화하고 변경할 수 없도록 통제합니다. 불변 객체를 수정해야 한다면 프로퍼티를 수정하는 대신 새로운 객체를 생성해야 합니다. 6 | 7 | ``` 8 | class Cash { 9 | private int dollars; 10 | public void mul(int factor) { 11 | this.dollars *= factor; 12 | } 13 | } 14 | 15 | class Cash { 16 | private final int dollars; 17 | public Cash mul(int factor) { 18 | return new Cash(this.dollars * factor); 19 | } 20 | } 21 | ``` 22 | 23 | 불변 객체는 자기 자신을 수정할 수 없고, 항상 원하는 상태를 가지는 새로운 객체를 생성해서 반환해야 한다. 24 | 25 | ## 불변객체를 사용해야 하는 이유 26 | 1. 식별자 가변성 - 가변 객체의 경우 다른 자료 구조를 사용할 때, 자료구조에 해당 가변 객체의 값을 넣어 둔 뒤 값을 변경할 경우 해당 자료구조 내에서 식별자가 변경되게되는 이슈가 생긴다. (ex. map) 27 | 2. 실패 원자성 - 완전하고 견고한 상태거나, 실패하거나 둘 중 하나의 상태를 갖는다. 따라서 불변 객체로 만든다면 실패 시에는 내부의 어떤 것도 수정할 수 없도록 하고, 성공 시 아예 새로운 객체를 반환하도록 할 수 있다. 만약 가변 객체라면 중간에 **일부만** 변경될 수 있는 위험이 존재한다. 28 | 3. 시간적 결합 - 어떤 객체를 선언할 때, setter를 이용해서 프로퍼티를 초기화한다면 초기화 순서에 따라 값의 상태가 달라질 수 있다. 하지만 불변 객체를 이용한다면 프로퍼티의 초기화는 생성자에서만 가능하므로 시간적 결합에 대한 이슈를 없앨 수 있다. 29 | 4. 부수효과 제거 - 객체가 가변인 경우 누구든 손쉽게 객체를 수정할 수 있다. (이 말 만으로도 4번은 증명되는 것 같다) 30 | 5. NULL 참조 없애기 - 3번과 관련있다. setter가 아닌 생성자에서 초기화함으로 NULL 이 생기는 것은 생성자에 null을 전달한 경우밖에 없다. 31 | 6. 스레드 안전성 - 불변이므로 당연하지 않을까? 32 | 7. 더 작고 더 단순한 객체 - 250줄을 넘는 클래스는 리팩토링이 필요하다. 불변 객체의 경우에는 프로퍼티를 생성자를 이용해 초기화해야하므로 가변 객체에 비해 더 작고, 더 단순한 객체가 될 수 밖에 없다. 33 | 34 | ## 의견 35 | 불변 객체의 장점은 매우 많다고 생각한다. 그리고 가변 객체에 비해 불변 객체를 이용하는 것이 더 좋은 방법인 것에도 동의한다. 그리고 책을 읽다 보니 #9 에서 불변 객체에서의 조정자는 객체를 리턴해야하는가? 에 대한 답으로 O를 할 수 있는 듯 ? 36 | 37 | 아무튼, 읽다보니 저자의 불변 객체 옹호는 이해가 되는데 그렇다면 가변 객체는 왜 필요할까? 38 | 이에 대해 stackoverflow의 [why we need mutable classes?](https://stackoverflow.com/questions/23616211/why-we-need-mutable-classes)에서 채택받은 답변을 내가 이해한 바로는 "A-Z까지의 객체를 생성할 때 중간 과정에서 많은 객체가 (낭비적) 생성이 될 수 있고, 또한 어차피 Z로 갈거라면 생성에 소비할 필요가 없다. 예를 들어 문자열 배열과 같은 경우, 어차피 1000개의 문자열을 연결하여 하나의 결과를 생성해야 할 때, 불변 객체를 이용한다면 중간에 낭비될 98개의 중간 개체를 생성해야한다. 또한 정렬이나 필터링과 같은 상황에서는 정렬이나 필터링되길 원하는 객체의 사이즈가 작다면 문제되지 않지만, 매우 크다면 정렬 후 새로운 컬렉션을 반환할 때 낭비가 될 수 있다." 는 이유로 가변 객체가 필요하다고 이해하였다. 이와 더불어 자료구조 같은 것들을 가변 객체를 이용해 더 쉽게 구현할 수 있을 것이라 생각함! 39 | 40 | 아무튼 불변 객체의 장점이 더 많지만, 가끔은 가변객체가 더 적절할 때가 있는 것 같다. 상황에 알맞게 사용하면 될 듯!? 41 | 42 | -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/neozal.md: -------------------------------------------------------------------------------- 1 | # 2.4 메서드 이름을 신중하게 선택하세요 2 | 3 | - 빌더 : 무언가를 만들어 내는 것 (반환값이 있는 것) 4 | - 명사로 만들어라 5 | - 조정자 : 상태를 변화시키는 것 (반환값이 없는 것) 6 | - 동사로 만들어라 7 | - 빌더와 조정자 혼합 8 | - 두가지 일을 따로 하는 객체를 만들어 반환하라 9 | - boolean 값을 결과로 반환하는 경우 10 | - 형용사로 지어야 한다. 11 | - 중복이 되는 is는 뺀다 (empty(), readable() ...) 12 | 13 | # 내 생각 14 | 아주 좋다고 생각한다. 객체를 객체로서 존중하며, 절차지향과 객체지향의 차이점을 극명히 드러내는 장이라고 생각한다. 15 | 16 | ## 빌터는 명사로 17 | 객체는 살아있는 유기체다. 따라서 객체에서 "무엇을 하라" 고 "세세하게" 명령하는것은 그 존재를 무시하는 행동을 하는 것 이다. 18 | 단지 그에게 "내가 이런게 필요한데 좀 줄 수 있니?" 라고 물어보며 그를 존중해 줘야한다. 19 | 20 | 사실 이런 설명이 너무 추상적이고 객체지향에 미친놈의 생각이라고 생각할 수도 있다. 그렇다면 캡슐화의 관점에서 설명해 보겠다. 21 | 22 | 우리가 객체를 만드는 이유는 무엇일까? 왜 캡슐화를 통해 행동을 감추는 걸까? 23 | 내 생각으로 가장 큰 이유는 클라이언트의 관심사를 객체로부터 분리하고, 기능의 유지보수에 용이하게 하기 위해서이다. 24 | 25 | 클라이언트의 관심사를 객체로부터 분리한다는건 무슨 의미일까? 바로 객체의 내부 구현에는 신경쓰지 않겠다는 의미이다. 26 | 클라이언트는 사용하는 객체의 내부 구현을 신경쓰지 않고 단지 public 인터페이스를 통해서만 메세지를 주고받기 때문에 객체의 내부에 변화가 일어나는지 전혀 알지 못한다. 27 | 즉, OCP가 만족된다. 이 이야기를 왜 했을까? 28 | 29 | 빌더를 명사로 만들지 않을 경우를 보자. 30 | 책에서 소개한 예시를 기반으로 한번 소개해 보겠다. 31 | ```java 32 | InputStream load(URL url); 33 | String read(File file); 34 | int add(int x, int y); 35 | ``` 36 | 위 메소드들의 공통점은 무엇인가? 모두 "객체의 행동"을 정의하고 있다. 37 | 객체에게 "로드하라", "읽어라", "더해라" 라고 명령한다. 이는 객체의 내부 구현을 클라이언트가 신경쓰게 된다는 것과 마찬가지다. 38 | 39 | 캡슐화에서 클라이언트는 사용하는 객체의 내부 구현을 몰라야한다. 그냥 그 객체가 뭔가해서 줄거라고 믿어야 한다. 40 | 철학적 접근이든 기술적 접근이든 저런 네이밍은 문제를 야기한다. 41 | 만일 위 load 메서드를 호출했는데 url을 load하지 않고 캐싱된걸 그냥 던져줬으면? 클라이언트는 이를 제대로 구현이 안된 메소드르 생각할 수도 있다. 42 | (사실 이런 관점은 크게 중요하지 않다. 객체지향적으로 생각하는게 더 중요하다.) 43 | 44 | ## 조정자는 동사로 45 | 조정자의 경우도 마찬가지다. 아마 위에서는 객체한테 뭐 시키지 말라더니 이번엔 왜 동사로 하냐고 태클이 들어올 수 있다. 46 | 하지만 빌더는 데이터를 받아오는 것에 관점이 있고, 조정자는 내부 상태를 수정하는 것에 관점이 있다는 것을 인지해야 한다. 47 | 조정자의 메서드 명을 동사로 하더라도 그 행위를 어떻게 하는지는 객체에 숨겨져 있다. 따라서 동사의 이름도 문제가 없다. 48 | 49 | ## 빌더와 조정자 혼합하기 50 | 사실, 이부분은 큰 공감을 못하겠다. 너무 원칙을 중시하다가 나아가 버린 느낌...? 그냥 메서드 이름에 하는 행동과 반환값까지 명시하면 되지 않을까? 51 | ```java 52 | class Document { 53 | int write(InputStream content); 54 | } 55 | -> 56 | class Document { 57 | int writeAndReturnSavedBytes(InputStream content); 58 | } 59 | ``` 60 | 이런식으로... 61 | 62 | ## boolean값을 결과로 반환하는 경우 63 | 예고르의 실수일까...? 이 절에서 예고르는 "읽을 수 있는 코드"를 중시하고 그걸 위해서 형용사를 추천한다. 64 | 하지만 그런 이유라면 굳이 형용사가 아니더라도 충반히 가능하다. 65 | 심지어 66 | ```java 67 | A.isExisting(); 68 | A.isExistable(); 69 | ``` 70 | 나는 전자의 경우가 더 자연스럽다고 생각한다... 71 | 그냥 개발자들의 관례에 맞게 쓰는편이 더 좋다고 생각한다. 72 | 73 | -------------------------------------------------------------------------------- /2장-학습/3절-항상-인터페이스를-사용하세요/gump.md: -------------------------------------------------------------------------------- 1 | # **2.3 -항상 인터페이스를 사용하세요** 2 | 3 | ## **정리** 4 | 5 | ### **객체들의 세계는 매우 사회적이면서 유대감이 높아요.** 6 | 7 | 객체들은 서로를 필요로 하기 때문에 **결합돼요.** 8 | 9 | 결합은 어떤 객체가 어떤 일을 수행행 해야하고, 다른 객체에게 어떤 서비스를 제공하는 지를 정확하게 알고 있게 해줘요. 그렇기에 유용해 보여요. 10 | 11 | 하지만 애플리케이션이 성장하고 객체들의 수가 많아질수록 **강한 결합도**가 심각한 문제로 떠올라요. 12 | 13 | ### **높은 의존성과 낮은 결합도를 항상 생각해야 해요.** 14 | 15 | 결합도 문제는 유지보수성에 영향을 미쳐요. 유지보수성이 가장 중요해요. 16 | 17 | ### **유지보수성을 높이기 위해선 최선을 다해서 객체를 분리해야해요.** 18 | 19 | 기술적인 관점에서 객체 분리란 `상호작용하는 다른 객체를 수정하지 않고도 해당 객체를 수정할 수 있도록 만든다는 것`이에요. (변경에는 닫혀 있어야 하고, 확장에는 열려 있어야함) 20 | 21 | 이를 가능하게 해주는 가장 훌륭한 도구가 **인터페이스**예요. 22 | 23 | ```java 24 | interface Car { 25 | Car go(); 26 | 27 | Car back(); 28 | 29 | int getDistance(); 30 | } 31 | ``` 32 | 33 | Car는 인터페이스에요. 다시 말해서 우리의 객체가 다른 객체와 의사소통하기 위해 따라야하는 **계약**이에요. 34 | 35 | ```java 36 | class DefaultCar implements Car { 37 | private final int distance; 38 | 39 | DefaultCar(int distance) { 40 | this.distance = distance; 41 | } 42 | 43 | @Override 44 | public Car go() { 45 | return new DefaultCar(distance + 1); 46 | } 47 | 48 | @Override 49 | public Car back() { 50 | return new DefaultCar(distance - 1); 51 | } 52 | 53 | @Override 54 | public int getDistance() { 55 | return double; 56 | } 57 | } 58 | ``` 59 | 60 | 자동차가 필요하다면, 실제 구현 대신 **계약**에 의존하면 돼요. 61 | 62 | ```java 63 | class People { 64 | private Car car; 65 | } 66 | ``` 67 | 68 | People 클래스는 Car 인터페이스의 구현 방법에 아무런 관심이 없어요. 메서드가 어떻게 동작하는지도 관심이 없어요. 즉 `동작 방식을 알지 못해`요. 69 | 70 | ### **모든 퍼블릭 메서드가 인터페이스를 구현하도록 만들어야해요.** 71 | 72 | 올바르게 설계된 클래스라면 최소한 하나의 인터페이스라도 구현하지 않는 퍼블릭 메서드를 포함해서는 안돼요. 73 | 74 | 즉 다음과 같이 설계해서는 안돼요 75 | 76 | ```java 77 | class Car { 78 | public int move(){ 79 | //작업 수행 80 | } 81 | } 82 | ``` 83 | 84 | move()는 어떤 것도 오버라이드 하지 않기 때문에 문제가 있어요. 이 설계는 클래스의 사용자(다른 클래스)로 하여금 이 클래스에 강하게 결합되도록 조장해요. 85 | 86 | 직접적으로 Car.move()를 사용할 수 밖에 없기 때문에 새로운 메서드를 이용해서 구현을 대체할 수 없어요. 87 | 88 | ### 클래스가 존재하는 이유는 다른 누군가 클래스의 서비스를 필요로 하기 때문이에요 89 | 90 | 서비스는 계약이자 인터페이스이기 때문에 클래스가 제공하는 서비스는 어딘가에 문서화 되어있어야해요. 91 | 92 | 서비스 제공자들은 서로 경쟁헤요. 이는 동일한 인터페이스를 구현하는 여러 클래스들이 존재한다는 말이에요. 그리고 각각의 경쟁자는 서로 다른 경쟁자를 쉽게 대체할 수 잇어야해요. 이것이 **느슨한 결합도**의 의미에요 93 | 94 | 즉, 요소들 사이의 계약으로서 인터페이스는 우리가 전체적인 환경을 구조화된 상태로 유지할 수 있도록 해줘요. 95 | 96 | ## **의견** 97 | 98 | 짧은 문장에 많은 뜻이 있어 정리하기 어려웠어요. 모든 클래스는 인터페이스를 구현 해야한다는 말이 지금은 어려워요. 이또한 의식적으로 연습해야 할 부분이라 생각해요. -------------------------------------------------------------------------------- /2장-학습/8절-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요/gump.md: -------------------------------------------------------------------------------- 1 | ## 2.8-모의-객체(Mock)-대신-페이크 객체(Fake)를-사용하세요 2 | 3 | ## 정리 4 | 5 | ### 모킹은 나쁜 프랙티스이며 최후의 수단으로만 사용해야해요 6 | 7 | 모킹 대신 '페이크 객체(fake object)'를 사용해요. 8 | 9 | ```java 10 | interface Exchange { 11 | float rate(String origin, String target); 12 | final class Fake implements Exchange { 13 | @Override 14 | public float rate(String origin, String target) { 15 | return 1.2345f; 16 | } 17 | } 18 | } 19 | ``` 20 | 21 | 이를 이용한 단위테스트 아래와 같아요. 22 | 23 | ```java 24 | Exchange exchange = new Exchange.Fake(); 25 | Cash dollar = new Cash(exchange, 500); 26 | Cash euro = dollar.in("EUR"); 27 | assert "6.17".equals(euro.toString()); 28 | ``` 29 | 30 | 단위테스트가 훨씬 짧아진 것을 확인할 수 있어요. 31 | 32 | ### '페이크(Fake)'클래스는 강력해져야 하고 '실제' 클래스보다 더 복잡해지는 경우도 있어요 33 | 34 | 페이크 클래스를 만족하도록 테스트를 작성하지 말고, 페이크 클래스가 테스트를 올바르게 지원하도록 만들어야해요. 35 | 36 | 페이크 클래스를 사용하면 테스트를 더 짧게 만들 수 있기 때문에 유지보수성이 눈에 띄게 향상되요. 37 | 38 | ### 모킹은 가정을 사실로 전환시키기 띠문애 단위 테스트를 유지보수하게 어렵게 만들어요 39 | 40 | 클래스의 공개된(public) 행동을 변경하지 않을 경우 **단위 테스트는 실패해서 안돼요**. 즉, 단위 테스트는 거짓 양성 지표를 제공해서는 안돼요. 41 | 42 | 하지만 모킹은 가정을 사실로 만들기 때문에, 아무런 이유 없이도 실패할 수 있어요. 43 | 44 | ### 모킹의 문제 예시 45 | 46 | ```java 47 | interface Exchange { 48 | float rate(String target); 49 | float rate(String origin, String target); 50 | } 51 | ``` 52 | 53 | 요구사항이 추가되어, 한 개의 인자를 받는 메서드를 추가 했다면, 실제 코드에서는 아무런 문제가 없지만 테스트 코드에서 에러가 발생할 수 있어요. (클래스는 여전히 잘 동작하지만, 테스트는 실패해요) 54 | 55 | ### 페이크를 쓴다면 56 | 57 | ```java 58 | interface Exchange { 59 | float rate(String target); 60 | float rate(String origin, String target); 61 | final class Fake implements Exchange { 62 | @Override 63 | public float rate(String target) { 64 | return this.rate("USD", target); 65 | } 66 | 67 | @Override 68 | public float rate(String origin, String target) { 69 | return 1.2345f; 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | 페이크 클래스가 존재하는 상황에서 Exchange 인터페이스를 변경하기 위해서는 자연스럽게 Exchage.Fake 클래스의 구현도 함께 변경해요. 76 | 77 | 이때는, 단위테스트를 변경할 필요가 없어요. 78 | 79 | ### 모킹은 단위 테스트를 지원하기 위해 만들어졌지만 실제로는 단위 테스트에 우호적이지 않아요. 80 | 81 | 모킹은 클래스 구현과 관련된 내부의 세부사항을 테스트와 결합시켜요. 82 | 83 | ### 반대로 페이크 객체를 사용하면 테스트를 충분히 유지보수 가능하게 만들 수 있어요. Cash 클래스와 Exchange 클래스 사이의 의사소통 방식에 대해서는 신경 쓸 필요가 없기 때문이에요. 84 | 85 | 우리가 Exchange 인스턴스를 Cash에게 제공하기 때문에, Cash가 Exchange를 사용하는 방법에 대해 알 권리가 우리에게 있다고 생각할수 있어요. 하지만 우리에게 객체의 구현 방법을 알 권리는 없어요. 테스트가 객체 내부의 구현 세부사항을 알면 테스트가 취약해지고 유지보수하기도 어려워져요. 86 | 87 | ### 페이크 클래스는 인터페이스의 설계에 관해 더 깊이 고민하도록 해줘요 88 | 89 | 인터페이스를 설계하면서 '페이크'클래스를 만들다보면 필연적으로 인터페이스의 작성자뿐만 아니라 사용자의 관점에서도 고민하게 해줘요. 인터페이스를 다른 각도에서 바라보고, '테스트'리소스를 사용해서 사용자와 동일한 기능을 구현해요 90 | 91 | ## 의견 92 | 93 | Mock 방식이 단위테스트를 위해 필연적으로 필요한 기술이라 생각했어요. 94 | 95 | 인터페이스에 Fake클래스를 추가하면, Mock을 쓰지 않아도 단위 테스트를 진행할 수 있다는 것이 신기해요. 96 | 97 | 아직 Fake클래스가 어색하지만, 다음 미션때 적용해보려 해요. -------------------------------------------------------------------------------- /3장-취업/3절-인자의-값으로-NULL을-절대-허용하지-마세요/gump.md: -------------------------------------------------------------------------------- 1 | # **3.3-인자의 값으로 NULL을 절대 허용하지 마세요** 2 | 3 | ## **정리** 4 | 5 | ### **이번 장에선 메서드의 인자값으로 NULL을 사용하는 경우를 살펴볼거에요** 6 | 7 | NULL을 허용하는 find() 메서드를 구현하기 위해서는 다음과 같이 분기를 처리할 필요가 있어요 8 | 9 | ```java 10 | public Iterable find(String mask) { 11 | if (mask == null) { 12 | //모든 파일을 찾는다. 13 | } else { 14 | // 마스크를 사용해서 파일을 찾는다. 15 | } 16 | } 17 | ``` 18 | 19 | 여기서 문제가 되는 부분은 mask == null 이에요. 이는 mask 객체에게 이야기하는 대신, 이 객체를 **피하고 무시해요.** 말 그대로 객체의 면전에 대고 "당신과 이야기 나눌 가치가 있나요?" 또는 "그 객체에게 말할 가치가 있나요?"라고 묻는 것이에요. 20 | 21 | 심지어 객체에게 직접 이야기하지도 않아요. 그 객체가 충분히 좋은지 아닌지를 객체를 알고 있을 것으로 추측되는 누군가에게 물어요.(예의 없는 사람이네요) 22 | 23 | ### **객체를 존중한다면 아래와 같이 행동해야해요** 24 | 25 | ```java 26 | public Iterable find(Mask mask) { 27 | if (mask.empty()) { 28 | //모든 파일을 찾는다. 29 | } else { 30 | // 마스크를 사용해서 파일을 찾는다. 31 | } 32 | } 33 | ``` 34 | 35 | 더 개선한다면 아래와 같아요 36 | 37 | ```java 38 | public Iterable find(Mask mask) { 39 | Collection files = new LinkedList<>(); 40 | for (File file : files) { 41 | if (mask.matches(file)) { 42 | file.add(file); 43 | } 44 | } 45 | return files; 46 | } 47 | ``` 48 | 49 | ### **mask 객체를 존중한다면 조건의 존재 여부를 객체 스스로 결정하게 해야해요.** 50 | 51 | 겉모습만으로 객체를 판단해서 안돼요. 52 | 53 | ### **인자의 값에 NULL을 허용하면 mask==null같은 비교문을 사용할 수 밖에 없어요** 54 | 55 | 이는 절차지향의 유산을 가져온 것이에요. 56 | 57 | 역참조란 메모리의 실제 값을 참조하는 것이에요. ( **p ) 58 | 59 | 데이터를 조작하는 절차적인 프로그래밍에서는 **바보같은 데이터**만 사용하기에 **역참조**를 사용해야 하지만, Java에서는 필요가 없어요. 60 | 61 | 그렇기에 여전히 null이 존재해야하는 이유는 전혀 없어요. (저자는 커다란 실수라고 말해요) 62 | 63 | ### **OOP에서 존재하지 않는 인자(absent argument) 문제는 널 객체(null object)를 이용해서 해결해야해요** 64 | 65 | 전달할 것이 없다면, 비어있는 것처럼 행동하는 객체를 전달하면 돼요. 66 | 67 | ```java 68 | interface Mask { 69 | boolean matches(File file); 70 | } 71 | ``` 72 | 73 | Mask 인터페이스의 적절한 구현은 글롭패턴( '*.txt'형식의 패턴, 나중에 알아보자)을 캡슐화하고 이 패턴에 대해 파일 이름을 매칭시킬 거에요. 반면에 Null 객체는 다음과 같이 구현 할 수 있어요. 74 | 75 | ```java 76 | class AnyFile implements Mask { 77 | @Override 78 | public boolean matches(File file) { 79 | return true; 80 | } 81 | } 82 | ``` 83 | 84 | AnyFile은 Mask의 특별한 경우로, 어떤 내부 로직도 포함하지 않아요. 어떤 파일을 전달하더라도 항상 true를 반환해요. 85 | 86 | 이제 Null 값을 전달하는 대신, AnyFile의 인스턴스를 생성해서 find()메서드에 전달하면돼요. 87 | 88 | find()메서드는 무슨 일이 일어나고 있는지 전혀 알지 못한 채, 여전히 올바른 Mask가 전달되었다고 생각할 거에요. 89 | 90 | ### **Null을 무조건 써야할 때 대응법** 91 | 92 | ### **1. 방어적인 방법으로, NULL을 체크한 후 예외를 던짐** 93 | 94 | ### **2. NULL을 가볍게 무시함** 95 | 96 | 인자가 절대 NULL이 아니라고 가정하고 어떤 대비도 하지 않아요. 메서드를 실행하는 도중에 인자에 접근하면 NullPointerException이 던져지고 메서드 호출자는 자신이 실수했다는 사실을 인지해요 97 | 98 | ### **요약, 메서드 인자로 절대로 NULL을 사용하지마세요** 99 | 100 | ## **의견** 101 | 102 | Null의 대응법이 조금 웃겼다. 103 | 104 | 실제 현업에서도 가볍게 무시할 수 있는지가 의문이긴하다.. 105 | 106 | (예고르 행님이 아무리 확고한 생각을 가지고 있어도 모두를 이해시킬 순 없었나보다) -------------------------------------------------------------------------------- /2장-학습/4절-메서드-이름을-신중하게-선택하세요/gump.md: -------------------------------------------------------------------------------- 1 | 2 | # 2.4 -메서드 이름을 신중하게 선택하세요 3 | 4 | ## **정리** 5 | 6 | ### 빌더는(Builder)의 이름은 명사로, 조정자(manipulator)의 이름은 동사로 지어요. 7 | 8 | ### **빌더(builder)** 9 | 10 | 빌더란 뭔가를 만들고 새로운 객체를 반환하는 메서드를 가르켜요. 11 | 12 | 빌더는 항상 뭔가를 반환해요. 13 | 14 | 빌더의 반환 타입은 절대 void가 될 수 없으며, 15 | 16 | 이름은 항상 명사여야 해요 17 | 18 | ### 빌더 예제 19 | 20 | ```java 21 | int pow(int base, int power); 22 | float speed(); 23 | Employee employee(int id); 24 | String parsedCell(int x, int y); 25 | ``` 26 | 27 | 마지막 메서드인 parsedCell()을 보면, 형용사인 parsed가 명사인 cell을 꾸미는 형태를 취하고 있어요. 이러한 방식은 앞에서의 원칙(빌명조동)을 위반하지 않아요. 오히려 의미를 풍부하게 해주니, 이 메서드가 어떤 형태로든 cell의 원래 내용을 변환한 후 반환할 것이라는 사실을 기대할 수 있어요. 28 | 29 |
30 | 31 | ### **조정자(manipulator)** 32 | 33 | 객체로 추상화한 실세계 엔티티를 수정하는 메서드를 가르켜요. 34 | 35 | 조정자의 반환타입은 항상 void이고, 36 | 37 | 이름은 항상 동사여야 해요. 38 | 39 | ### **조정자 예제** 40 | 41 | ```java 42 | void save(String content); 43 | void put(String key, Float value); 44 | void remove(Employee emp); 45 | void quicklyPrint(int id); 46 | ``` 47 | 48 | 마지막 메서드인 quicklyPrint()를 보면, 부사인 quickly가 동사인 print를 꾸며주고 있어요. 이 또한 동사에 풍부한 의미를 사용하니 원칙에 위배되지 않아요. 49 | 50 | ### **잘못된 예제** 51 | 52 | ```java 53 | int save(String content); 54 | boolean put(String key, Float value); 55 | float speed(float val); //빌더인 동시에 조정자이기 때문에 잘못됨 56 | ``` 57 | 58 | save메서드는 조정자이기 때문에 잘못됐어요, void를 반환하거나 bytesSaved()로 변경해야해요. 59 | 60 | put 메서드는 조정자처럼 동작하지만 빌더처럼 boolean을 반환하기에, 잘못됐어요. PutOperation 인스턴스를 반환해 클래스의 전반적인 설계를 수정해야해요. 즉, PutOperation 클래스는 조정자인 save()메서드와 성공/실패 여부를 반환하는 빌더인 success()메서드를 포함할 것입니다. 61 | 62 | speed 메서드 또한 빌더인 동시에 조정자에요. put메서드와 마찬가지로 SaveSpeed 클래스를 추가하도록 해요 . 63 | 64 | ### **빌더는 객체로부터 뭔가를 얻기를 바랄 때 사용해요.** 65 | 66 | 즉, 우리는 객체로부터 뭔가를 얻어요. 다시 말해 뭔가를 만들라고 객체에게 요청해요. 어떻게 줄지는 요청한 객체가 알아서 하도록 맡겨요. 67 | 68 | ### **메서드의 이름을 동사로 지을 때에는 객체에게 "어떻게 할지"가 아닌, "무엇을 할 지"를 알려줘야해요.** 69 | 70 | 어떻게, 만들라고 요청하는 것은 협력자에 대한 존중이 없을 뿐더러 예의에도 어긋나는 방식이에요. 71 | 72 | 무엇을 만들어야 하는 지만 요청하고, 만드는 방법은 객체 스스로 결정하도록 해야해요. 73 | 74 | ### **조정자는 객체에게 일을 하도록 지시하지만, 얻기를 바라진 않아요.** 75 | 76 | 오직 빌더만이 값을 반환할 수 있고 이때 빌더의 이름은 명사에요. 객체가 뭔가를 조정해야 한다면 이름은 동사이고 반환값은 없어요. 77 | 78 | ### **빌더와 조정자가 혼합되어 있는 경우 클래스를 하나더 만들어요.** 79 | 80 | ```java 81 | class Document { 82 | int write(InputStream content); //잘못됨(빌더와 조정자를 혼합) 83 | } 84 | ``` 85 | 86 | 위와 같이 정의되어있다 할 때, 87 | 88 | 아래와 같이변경 할 수 있어요. 89 | 90 | ```java 91 | class Document { 92 | OutputPipe output(); 93 | } 94 | 95 | class OutputPipe { 96 | void write(InputStream content); 97 | int bytes(); 98 | long time(); 99 | } 100 | ``` 101 | 102 | ### Boolean은 값을 반환하기 때문에 빌더에 속하지만, 가독성 측면에서 형용사로 지어요. 103 | 104 | ```java 105 | boolean empty(); //is empty 106 | boolean readable(); //is readable 107 | boolean negative(); //is negative 108 | ``` 109 | 110 | 메서드를 읽을때 is 가 붙어있다고 생각하고 읽어요. 111 | 112 | ```java 113 | boolean equals(Object obj); 114 | boolean exists(); 115 | ``` 116 | 117 | 위와 같은 메서드에서는 문제가 될 수 있어요. is를 붙이면 isEquals와 isExists라는 올바르지 않은 문장이 만들어 지기 때문이에요. 118 | 119 | ```java 120 | boolean equalTo(Object obj); 121 | boolean present(); 122 | ``` 123 | 124 | 위와 같이 짓기로 해요. 125 | 126 | ### 메서드는 빌더나 조정자, 둘중 하나여만 해요. 결코 빌더인 동시에 조정자여서는 안되요. 127 | 128 | 빌더는 명사로, 조정자는 동사로 이름을 지어요. 129 | 130 | Boolean값을 반호나하는 빌더는 예외에요 이때는 형용사로 지어요. 이게 끝이에요. 131 | 132 | ## **의견** 133 | 134 | 개발을 하다보면 네이밍을 항상 신경써야해요. 이러한 고민하는 시간 줄일 수 있게 빌더, 조정자, boolean으로 정형화 했다는 점은 좋은 방법이에요. 135 | 136 | 하지만 클린코드에서 제시한, 제가 이전에 주로 사용했던(메서드 이름은 동사, 변수이름은 명사, 클래스 이름은 명사 ), 네이밍 방법과는 또 다른 방법이라, 거부감이 약간 들어요. 137 | 138 | 이 괴리감을 줄이는게 먼저 선행되야 할 것 같아요. -------------------------------------------------------------------------------- /2장-학습/9절-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요/gump.md: -------------------------------------------------------------------------------- 1 | ## 2.9-인터페이스를-짧게-유지하고-스마트(smart)를-사용하세요 2 | 3 | ## 정리 4 | 5 | ### 클래스가 다수의 인터페이스를 구현하기 때문에 인터페이스를 작게 만드는 것은 클래스를 작게 만드는 것보다 훨씬 중요해요. 6 | 7 | ```java 8 | interface Exchange { 9 | float rate(String target); 10 | float rate(String origin, String target); 11 | } 12 | ``` 13 | 14 | 이 인터페이스는 너무 많은 것을 **요구하기** 때문에 설계 관점에서는 형편 없는 인터페이스에요. 15 | 16 | 인터페이스는 고현 클래스가 준수해야 하는 계약이에요. 현재 위의 인터페이스는 구현자에게 너무 많은 것을 요구하기에. 이런 종류의 계약은 단일 책임 원칙(Single Responsibility Principle)을 위반하는 클래스를 만들도록 부추겨요. 17 | 18 | 하나의 인자를 받는 rate() 메서드는 이 인터페이스에 포함되어서는 안돼요. 19 | 20 | 이를 해결하기 위해서 필요한 것이 **'스마트(smart)'클래스**에요. 21 | 22 | ```java 23 | interface Exchange { 24 | float rate(String origin, String target); 25 | 26 | final class Smart { 27 | private final Exchange origin; 28 | 29 | public Smart(Exchange origin) { 30 | this.origin = origin; 31 | } 32 | 33 | public float toUsd(String source) { 34 | return this.origin.rate(source, "USD"); 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | 이러한 '스마트'클래스는 아주 명확하고 공통적인 작업을 수행하는 많은 메서드들을 포함할 수 있어요. 이 '스마트'클래스는 Exchange 인터페이스가 어떻게 구현되고 환율이 어떻게 계산되는지는 모르지만, 인터페이스 위에 특별한 기능을 적용해요. 41 | 42 | 이 기능은 Exchange의 서로 다른 구현 사이에 공유될 수 있어요. 43 | 44 | ### 스마트 클래스를 인터페이스와 함께 제공해야 하는 또 다른 이유는 인터페이스를 구현하는 서로 다른 클래스 안에 동일한 기능을 반복해서 구현하고 싶지 않기 때문이에요 45 | 46 | 요구사항이 추가되어 USD를 EUR로 변환을 해야하는 기능을 추가해야한다면, 이는 인터페이스에 추가하는 것이 아닌, '스마트'클래스에 추가하는 것이 좋아요. 47 | 48 | ```java 49 | float rate = new Exchange.Smart(new NYSE()) 50 | .toUsd("EUR"); 51 | ``` 52 | 53 | 환율 변환이 필요한 모든 위치에서 'EUR'이라는 문자열 리터럴을 반복적으로 사용하고 싶지는 않아요. 이때 필요한 것은 USD를 EUR로 변환해주는 eurToUsd()메서드에요 54 | 55 | ```java 56 | interface Exchange { 57 | float rate(String origin, String target); 58 | 59 | final class Smart { 60 | private final Exchange origin; 61 | 62 | public Smart(Exchange origin) { 63 | this.origin = origin; 64 | } 65 | 66 | public float toUsd(String source) { 67 | return this.origin.rate(source, "USD"); 68 | } 69 | 70 | public float eurToUsd() { 71 | return this.toUsd("EUR"); 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | 이제 단 한번의 메서드 호출만으로 EUR에서 USD로의 환율을 얻을 수 있어요. 78 | 79 | ```java 80 | float rate = new Exchange.Smart(new NYSE()) 81 | .eurToUsd(); 82 | ``` 83 | 84 | ### '스마트'클래스의 크기는 점점 더 커지겠지만, Exchange 인터페이스는 작고, 높은 응집도를 유지할 수 있어요. 85 | 86 | 인터페이스에는 오직 하나의 메서드만 선언하고, NYSE, XE, Yahoo 등의 환율 제공자는 이 메서드를 구현애요. 87 | 88 | 모든 거래소들이 기능을 공유할 수 있기 때문에 각 거래소들이 해당 기능을 개별적으로 구현할 필요가 없어요. 89 | 90 | 이것이 바로 이번 섹션의 제목과 주제를 "인터페이스를 짧게 유지하세요"로 정한 이유에요. 91 | 92 | ### 스마트 클래스는 네트워크 호출에 대한 어떤 것도 알아서는 안돼요 93 | 94 | ### 기본적으로 인터페이스를 짧게 만들고 '스마트' 클래스를 인터페이스와 함께 배포함으로써 공통 기능을 추출하고 코드 중복을 피할 수 있어요. 95 | 96 | ### 예제 97 | 98 | [https://github1s.com/jcabi/jcabi-github/blob/HEAD/src/main/java/com/jcabi/github/Blob.java](https://github1s.com/jcabi/jcabi-github/blob/HEAD/src/main/java/com/jcabi/github/Blob.java) 99 | 100 | ## 의견 101 | 102 | 앞 절의 Fake 객체는 테스트를 간소화 하기에 필요하고, 테스트의 의미를 더 강하게 해주기에 그 의미가 잘 다가왔어요. 103 | 104 | 하지만 이번 절의 Smart클래스는 한번도 사용하지 않아서 그런지 쉽게 이해가 되지않아요. 105 | 106 | 인터페이스에 기능을 추가하지 않고, 인터페이스 내부의 클래스에 그 기능을 추가하는게 어떤 효과가 있는지. 어려워요. 중복을 줄이기 위해 과한 설계가 요구되는게 아닌가 싶어요. -------------------------------------------------------------------------------- /2장-학습/5절-퍼블릭-상수를-사용하지-마세요/gump.md: -------------------------------------------------------------------------------- 1 | 2 | # **2.5 -퍼블릭 상수(Public Constant)를 사용하지 마세요** 3 | 4 | ## **정리** 5 | 6 | ### 상수를 이용한 공유 메커니즘은 캡슐화와 객체지향적인 사고 전체를 부정하는 일이에요 7 | 8 | 아래 예제는 잘 사용된 예제에요 9 | 10 | ```java 11 | class Records { 12 | private static final String EOL = "\r\n"; 13 | 14 | void write(Writer out) { 15 | for (Record rec : this.all){ 16 | out.write(rec.toString()); 17 | out.write(Records.EOL);; 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | 필요할때 마다 반복적으로 EOL을 사용하고 싶지 않기에. 객체 내부에서만 사용되는 EOL은 잘 사용되고 있어요. 24 | 25 | 하지만 아래와 같이 새로운 객체가 생성됐다고 가정해보면, 26 | 27 | ```java 28 | class Rows { 29 | private static final String EOL = "\r\n"; 30 | 31 | void write(PrintStream pnt) { 32 | for (Row row : this.all){ 33 | pnt.print( 34 | "{ $s }%s", row, Rows.EOL 35 | ) 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | 처음 작성했던 객체와 위의 객체는 공통점이 전혀 없어요. 하지만 EOL이라는 private 상수를 정의하고 있어요. 42 | 43 | 이때 중복을 제거해야해요. 44 | 45 | ### 중복을 제거하는 방법? 46 | 47 | ```java 48 | public class Constants { 49 | public static final String EOL = "\r\n"; 50 | } 51 | ``` 52 | 53 | 중복 제거를위해 위와 같이 선언했어요. 54 | 55 | ```java 56 | class Records { 57 | void write(Writer out) { 58 | for (Record rec : this.all){ 59 | out.write(rec.toString()); 60 | out.write(Constants.EOL);; 61 | } 62 | } 63 | } 64 | 65 | class Rows { 66 | void write(PrintStream pnt) { 67 | for (Row row : this.all){ 68 | pnt.print( 69 | "{ $s }%s", row, Constants.EOL 70 | ) 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | 중복 문제를 '해결'한 것 처럼 보이지만, 중복을 피하려다 더 큰 문제를 추가해버리는 꼴이 됐어요. 77 | 78 | 첫 번째 문제는 **결합도(coupling)**가 높아 진 것이고, 두 번째 문제는 **응집도(cohesion)**가 낮아진 것이에요. 79 | 80 | ### 상수는 멍청해요 81 | 82 | 자신의 존재 이유를 알지 못하는 텍스트 덩어리에 불과해요. 즉, 어디에서도 쓰이는 상수는 삶의 의미가 명확하지 않아요. 83 | 84 | ### 객체 사이에 데이터를 중복해서는 안돼요, 대신 기능을 공유할 수 있도록 새로운 클래스를 만들어야 해요 85 | 86 | ```java 87 | class EOLString { 88 | private final String origin; 89 | 90 | EOLString(String src) { 91 | this.origin = src; 92 | } 93 | 94 | @Override 95 | public String toString() { 96 | return String.format("%s\r\n", origin); 97 | } 98 | } 99 | ``` 100 | 101 | 계약에 의해 결합되었고, 계약에 의한 결합은 언제든지 분리가 가능하기에 유지보수성을 저하시키지 않아요. 102 | 103 | EOL은 계약에 따라 행동하며 내부에 계약의 의미를 캡슐화 해요. 104 | 105 | EOLString이 변경되어야 한다고 하더라도 객체들과 계약(인터페이스)은 동일하게 유지하면서 동작은 변경할 수 있어요. 106 | 107 | ```java 108 | class EOLString { 109 | private final String origin; 110 | 111 | EOLString(String src) { 112 | this.origin = src; 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | if (/* 맥북이면 */) { 118 | throw new IllegalStateException("맥에선 실행불가!"); 119 | } 120 | return String.format("%s\r\n", origin); 121 | } 122 | } 123 | ``` 124 | 125 | ### **퍼블릭 상수마다 계약의 의미를 캡슐화하는 새로운 클래스를 만들어야 하나요?** 126 | 127 | 맞아요 128 | 129 | ### 수 백개의 단순한 상수 문자열 리터럴 대신 수백 개의 마이크로 클래스를 만들어야 하나요? 130 | 131 | 맞아요. 132 | 133 | ```java 134 | String body = new HttpRequest() 135 | .method("POST") 136 | .fetch(); 137 | ``` 138 | 139 | 위의 코드는 아래로 변경할 수 있어요. 140 | 141 | ```java 142 | String body = new HttpRequest() 143 | .method(HTTPMethods.POST) 144 | .fetch(); 145 | ``` 146 | 147 | 이정도도 깔끔하지만, OOP정신에 어긋나요. 148 | 149 | 리터럴을 사용하는 대신, 아래와 같이 HTTP 메서드를 표현하는 단순한 클래스를 많이 만드는게 좋아요 150 | 151 | ```java 152 | String body = new PostRequest(new HttpRequest()) 153 | .fetch(); 154 | ``` 155 | 156 | ### 열거형(Enum) 클래스도 사용하지 말아요. 157 | 158 | ## **의견** 159 | 160 | 이번 절에는 꽤나 단정적인 문장들이 많아요. 어떠한 지식을 이렇게 강제로 주입받는 것은 처음이네요. 161 | 162 | OOP란 무엇일까에 대해 항상 고민했는데, 이러한 부분들부터 OOP적으로 만드는게 진정한 객체지향이 아닐까 생각이 들긴해요. 163 | 164 | 클래스들이 많아지는건 정말.. 어렵지만 이해해보려 해요. -------------------------------------------------------------------------------- /3장-취업/4절-충성스러우면서-불변이거나,-아니면-상수이거나/gump.md: -------------------------------------------------------------------------------- 1 | # **3.4-충성스러우면서 불변이거나, 아니면 상수이거나** 2 | 3 | ## **정리** 4 | 5 | ### 세상을 충분히 불변 객체로 모델링 할 수 있어요. 사람들이 혼란스러워 하는 이유는 서로 다른 개념인 `상태(state)`와 `데이터(data)`에 관해 오해하기 때문이에요 6 | 7 | 예제를 먼저 볼게요 8 | 9 | ```java 10 | class WebPage { 11 | private final URI uri; 12 | 13 | WebPage(URI path) { 14 | this.uri = path; 15 | } 16 | 17 | public String content() { 18 | // HTTP GET 요청을 전송해서, 19 | // 웹 페이지의 컨텐츠를 읽은 후, 20 | // 읽혀진 컨텐츠를 UTF-8 문자열로 반환 21 | } 22 | } 23 | ``` 24 | 25 | 이때의 WebPage는 불변일까요 가변일까요? 26 | 27 | ### 핵심은 객체가 살아있는 동안 상태가 변하지 않는다는 사실이에요 28 | 29 | 위의 예제에서의 WebPage는 분명히 불변이에요. 30 | 31 | 이부분이 많은 사람들이 오해하는 부분이에요 32 | 33 | ### 사람들은 불변 객체의 메서드를 호출할 때마다 상수처럼 매번 동일한 데이터가 반환되리라 기대해요 34 | 35 | 불변 객체가 생성되고 나면 모든 메서드가 항상 동일한 값을 반환하여, 반환된 결과를 100% 에측할 수 있다는 말은 적절해보이지만, 이런 사고방식은 틀렸어요. 36 | 37 | 상수처럼 동작하는 객체는 단지 불변성의 `특별한 경우(corner case)`일 뿐이기 때문이에요. 38 | 39 | ### 불변객체는 그 이상이에요 40 | 41 | 비록 content() 메소드 결과를 예측할 수 없더라도 WebPage는 불변 객체에 속해요. WebPage 클래스는 실제 웹 페이지와 통신하기 때문에 우리는 이 객체가 무엇을 돌려줄지 몰라요. 42 | 43 | 객체의 행동을 예상할 수는 없지만, 그럼에도 이 객체는 불변이에요. 결과가 변하기 때문에 `상수`는 아니지만, 객체가 대표하는 엔티티에 `'충성하기(loyal)`' 때문에 불변 객체로 분류돼요. 44 | 45 | ### 객체란 디스크에 있는 파일, 웹 페이지, 바이트 배열, 해시맵, 달력의 월과 같은 실제 엔티티(real-life)의 대표자예요. 46 | 47 | 여기서 실제라는 것은 가시성 범위(scope of visibility)밖에 존재하는 모든 것을 의미해요. 48 | 49 | 아래 코드에서 객체 f는 디스크에 저장되어 있는 파일을 대표해요 50 | 51 | ```java 52 | public void echo() { 53 | File f = new File("/tmp/test.txt"); 54 | System.out.println("File size: %d". file.length()); 55 | } 56 | ``` 57 | 58 | 여기서 f의 가시성 범위는 echo() 메서드의 '경계'에 대응해요. 예제에서는 디스크에 저장된 파일의 사이즈를 알아내기 위해 객체 f의 length() 메서드와 의사소통하고 있어요. 59 | 코드에서 `f는 '/tmp/test.txt'파일의 대표자`예요. 객체 f는 실제 파일의 입장을 대변해요. 우리의 관점에서 객체 f는 echo() 메서드 안에서만큼은 **파일**이에요 60 | 61 | 디스크에 저장된 파일을 다루기 위해 객체는 파일의 **좌표(coordinates)**를 알아야해요. 이 좌표를 다른 말로 객체의 **상태(state)**라고 불러요 62 | 63 | WebPage 객체의 상태는 페이지 URI예요. WebPage 객체는 HTTP 프로토콜을 이용해 실제 웹 페이지를 읽어들일 때 HTTP 엔드 포인트의 좌표로 이 URI를 사용해요. File 클래스의 상태는 파일 시스템 상에 위치한 파일의 전체 경로예요. 따라서 앞의 예제에서 객체 f의 상태는 /tmp/test.txt예요. 64 | 65 | ### 기본적으로, 모든 객체는 식별자(identity), 상태(state), 행동(behavior)를 포함해요 66 | 67 | `식별자`는 f를 다른 객체와 구별해요. `상태`는 f가 디스크 상의 파일에 대해 알고 있는 거에요. `행동`은 요청을 수신했을 때 f가 할 수 있는 작업을 나타내요. 68 | 69 | ### 불변객체와 가변객체의 중요한 차이는 불변 객체에는 식별자가 존재하지 않으며, 절대로 상태를 변경할 수 없다는 점이에요 70 | 71 | 동일한 URI를 가진 두 개의 WebPage 인스턴스를 생성하는 경우 두 개중 무엇을 사용하더라도 아무런 차이가 없기 때문에 두 객체는 동일해요. (객체 팩토리를 완벽하게 구현하려면, 동일한 상탤르 캡슐화 하는 중복된 인스턴스는 생성하지 않아야해요) 72 | 73 | ### 하지만 Java를 포함한 대부분의 OOP 언어에서는 상태가 동일하더라도 서로 다른 객체라고 판단해요 74 | 75 | 기본적으로 각 객체는 재정의할 수 있는 자신만의 유일한 식별자를 가져요 76 | 77 | WebPage 클래스 객체들이 포함하는 유일한 상태(식별자)는 URI형태의 페이지 좌표뿐이에요. 78 | 79 | 반변에 가변 객체는 완전히 다른 방식으로 동작해요. `가변 객체의 상태는 변경이 가능`하기 때문에, 상태에 독립적인 식별자를 별도로 포함해야 해요 80 | 81 | ### 완전한 객체 지항세계에서는 불변 객체만 존재하기에 equals()와 hashCode()라는 메서드는 필요하지 않아요 82 | 83 | 두 메서드의 구현은 모든 클래스에 걸쳐 동일해요. 새롭게 구현하거나 재정의할 필요가 없어요. 84 | 85 | 캡슐화된 상태만으로도 불변 클래스의 모든 객체들을 식별할 수 있기 때문이에요. 86 | 87 | ### 상수 리스트(constant list)와 불변 리스트(immutable list) 88 | 89 | 불변 객체의 컬렉션을 구현하려면 이 두가지 쟁점이 있어요. 90 | 91 | ### 상수 리스트 92 | 93 | ```java 94 | public class Cards { 95 | private final List cards; 96 | 97 | public Cards(final List cards) { 98 | this.cards = new ArrayList<>(cards); 99 | } 100 | 101 | public List getCards() { 102 | return Collections.unmodifiableList(cards); 103 | } 104 | 105 | public Cards add(final Card card) { 106 | List cards = new ArrayList<>(this.cards); 107 | cards.add(card); 108 | 109 | return new Cards(cards); 110 | } 111 | ``` 112 | 113 | 위와 같은 객체는 불변 객체라 부르는 것 보단 `상수 객체`로 부르는 것이 더 좋아요. 114 | 115 | cards는 Cards의 상태인 동시에 Cards가 대표하는 엔티티와 동일해요. 객체는 리스트를 대표하며, 이 객체의 상태는 바로 그 리스트예요. 116 | 117 | ### 불변 리스트 118 | 119 | ```java 120 | public class Cards { 121 | private final List cards; 122 | 123 | public Cards(final Card... cards) { 124 | this(Arrays.asList(cards)); 125 | } 126 | 127 | public Cards(final List cards) { 128 | this.cards = new ArrayList<>(cards); 129 | } 130 | 131 | public List getCards() { 132 | return Collections.unmodifiableList(cards); 133 | } 134 | 135 | public void add(final Card card) { 136 | this.cards.add(card); 137 | } 138 | } 139 | ``` 140 | 141 | add를 한다고 해서 가변이 되는 것이 아니에요. 142 | 143 | 객체를 대표하는 상태를 가지고 있고, 이는 변하지 않아요. 144 | 145 | (final시 리스트의 요소들의 불변을 보장하는 것이 아닌, 리스트 변수가 가르키고 있는 메모리의 불변을 보장) 146 | 147 | 즉 위의 객체도 불변이 맞아요. 148 | 149 | ### 상수 객체가 설계하고, 유지보수하고, 이해하기 더 편하기 때문에 불변 객체보다는 상수 객체를 사용하는 편이 더 좋아요. 150 | 151 | 2.6에서 불변 객체에 대해 이야기했던 대부분의 내용은 불변 객체의 특별한 경우인 상수 객체와 관련이 있어요. 152 | 153 | ## **의견** 154 | 155 | 이전에 나봄과 일급 컬렉션의 불변에 대해 얘기한적이 있는데, 이부분을 더 자세히 볼걸 그랬어요. 156 | 157 | 같은 불변이지만, 상수라 표현함으로써 불변성이 더 커지는 것을 알게됐어요. --------------------------------------------------------------------------------