[JAVA] 객체지향 프로그래밍
- 16 mins주의
아래 내용은 비정기적으로 수정됩니다.
Description
기초적인 JAVA 문법만 아는 초보들을 위한 객체지향설계개념 과 코딩컨벤션.
INDEX
1. Java Coding Convention
Oracle의 공식 Java 8 Coding Convention을 참조하였다.
Java 코드 작성 시 어떤 규칙을 지켜야 하는지 Oracle의 공식 가이드에 자세히 설명되어있다. 평소에 모든 가이드 내용을 숙지하고 있기 어렵기 때문에 자주 틀리는 공백(White Space) 규칙을 정리하겠다.
Blank Lines
공백행은 논리적으로 관련된 코드 섹션을 나누어 가독성을 향상 시킴
빈 줄 두 칸
- Class 와 Interface 정의 사이
- 소스파일의 섹션 사이
빈 줄 한 칸
- 메소드 사이
- 메소드의 지역 변수와 첫 번째 명령문 사이
- 가독성을 향상시키기 위해 메소드 내 논리적인 영역 사이에 공백라인 추가
- block 주석과 한 줄 주석 전에
Blank Spaces
- 키워드 뒤에 괄호가 오면 공백을 추가해야한다.
Tip: 공백은 메서드 이름과 여는 괄호 사이에 사용하면 안됩니다. ex) public void method() {}
- 모든 이진연산자를 피 연산자와 구분하기 위해 공백을 사용해야 한다. 단항 연산자의 경우 피연산자와 공백없이 붙여야한다.
- 인수 리스트의 쉼표 뒤에는 공백이 와야한다.
-
형변환(Cast)할 형태와 값 사이에는 공백이 있어야 한다.
-
for문의 표현식은 공백으로 구분되어야 한다.
2. 자바는 객체지향언어다
스프링 입문을 위한 자바 객체지향의 원리와 이해를 참고하여 작성
*"중복은 해악이다."*
2-1 Class vs Object
클래스 vs 객체
- 객체 란 유일무이하게
식별
이 가능한 것을 말한다.- 세상에 존재하는 모든 ‘사물’.
- ‘실체’하는 유일한 것
- ‘속성’과 ‘행위’를 갖는 것
- 클래스 란 객체들의
분류
이다.- ‘분류’
- ‘개념’
보통 클래스와 객체의 차이를 답하라 하면 많은 사람들이 붕어빵
과 붕어빵틀
의 예시를 든다. 보통 Class
를 생성하여 Object
를 만들 수 있다 생각하기 때문에 붕어빵틀
로 붕어빵
을 만들 수 있다는 것이다.
간단히 코드로 옮겨보면
붕어빵틀
로 붕어빵
을 만들었는데 타입이 붕어빵틀
이란 것은 말이 안되는 코드다. 붕어빵틀
은 붕어빵
에 대한 클래스 가 아니라 붕어빵
에 대한 팩토리클래스 이다. (팩토리 클래스는 다른 객체를 생성하는 클래스다.)
이렇게 되어야 한다.
객체 식별하기
객체와 클래스를 구별짓는 가장 중요한 질문은 이것이 ‘분류’인가 ‘사물’인가이다.
객체는 유일한 identity를 갖는다.
나이, 제조년월을 물었을 때 답할 수 있다면, 객체. 없다면 클래스이다.
피켜스케이터 김연아 = new 피겨스케이터();
// 김연아 -> 객체
자동차 아버지의소나타 = new 자동차();
// 자동차 -> 클래스
// 아버지의소나타 -> 객체
고양이 냐옹이 = new 냐옹이();
// 고양이 -> 클래스
// 냐옹이 -> 객체
때문에 객체를 구현할 때 이름이 중요하다. 객체는 분류의 명칭을 가지면 안된다.
Student student = new Student(); // x
Student james = new Student(); // o
2-2 객체지향 4대 특성
2-3 객체지향 설계 5원칙
SOLID
- SRP (Single Responsibility Principle) 단일 책임 원칙
- OCP (Open Closed Principle) 개방 폐쇄 원칙
- LSP (Liskov Substitution Principle) 리스코프 치환 원칙
- ISP (Interface Segregation Principle) 인터페이스 분리 원칙
- DIP (Dependency Inversion Principle) 의존 역전 원칙
2-4 Tip
- 객체를 객체답게 쓰는 법
인스턴스 변수와 인스턴스 메소드가 100% 의존관계를 맺도록 한다. 인스턴스 변수와 무관한 메소드는 있으면 안된다.
- 패키지 명은 항상 소문자로 작성
util, domain, view
- 변수명에는 자료형을 쓰지말아라.
List
carList; (x) List cars; (o) -
상태값이 변하는 값은 인스턴스 변수로, 상태값이 변하지 않는 값은 static 변수로 만들어라.
- 인스턴스 변수는 필요할 때만 선언하라. 다른 변수를 이용해서 구할 수 있다면 중복
-
메소드에서 else를 쓰지 마라. else를 쓰지 않을 경우 코드가 간결해지고 메소드의 역할이 명확해진다.
-
생성자가 두 종류 이상일 때, Factory Method로 구현하는 방식을 명시한다.
다형성
-
다형성을 잘 활용하면 서로간에 영향을 미치지 않은 상태에서 기능을 확장할 수 있다.
-
“조합” (Composition) : A가 B를 가진다. (has a)
- “상속” (Inheritance) : A는 B다. (is a). 핵심로직일 경우 외부에서 접근하지 못하게하고 자식만 접근하도록 protected 키워드를 쓸 것. ‘부모는 자식을 가리킬 수 있다.’
-
여러 클래스의 일부분만 ‘중복’일 때
abstract 클래스
의abstract 메소드
로 만들면 중복을 제거할 수 있다. - Interface : ‘어떤 것’들의 표준을 잡는 것 (구현되지 않은 메소드들의 집합)
인터페이스는 구현을 '강제' 외부와의 접점이기에 public 키워드가 없어도 메소드를 구현할 때 무조건 public으로 구현 인터페이스는 객체가 무엇(변수)을 가지는 지는 관심없다. 어떤 행위(메소드)를 해야하는지만이 중요 만약 추상클래스에 추상 메소드만이 존재할 때는 추상클래스가 아닌 인터페이스로 구현
- Abstract(추상) vs Interface(인터페이스) :
중복되는 부분이 있을 때 중복되는 부분을 abstract class의 메소드로 추출한다. 추상클래스의 역할은 인터페이스와 실제 구현할 클래스 사이에 중복되는 메소드를 제거하는 역할을 한다.
중복을 제거한 클래스의 계층
inteface A
abstract class B implements A
class C extend abstract B
-
인터페이스는 보안에 ‘유리’하다. : 내부로직이 들어나지 않고 ‘행위’만 노출하기 때문에. 구현체를 노출하지 않는다.
-
인터페이스 기반으로 객체를 설계할 때 구현체를 노출하는 것이 아니라 인터페이스를 노출시켜라.
3. Design Pattern
3-1 MVC 패턴
재사용성을 높여 비용을 절감하는 것. 특히 모델(핵심 로직)을 재사용하기 위해. 뷰와 컨트롤러만 바꾸어서 Web -> App -> API 등으로 사용할 수 있다. 핵심로직은 잘 바뀌지 않는다. 제일 많이 변경되는 곳은 View다.
- MVC : 확장성 있는 설계를 위해 핵심로직과 UI는 분리할 것
Model : 핵심로직(Domain) Controller : View에서 값을 얻어 Model에 분배하고 다시 Model에서 값을 받아 View로 전달하는 역할 View : 사용자에게 입력, 출력을 하는 부분
- DTO : Data Transfer Object. Data 전달을 위해 필요한 객체. => MutableObject
핵심로직을 수행하는 Model과 데이터 영역을 분리 (Model 캡슐화) View에 Data만 전달하기 위해 필요한 객체. getter와 setter를 사용하여 상태값을 변경하고 얻어 낼 수 있다. 단, 자신의 값은 직접 변경하게 외부에서는 메시지를 보내는 형태로 값을 변경시켜야한다.
- VO : Value Object => ImmutableObject
VO == DTO 는 틀린말 DTO는 java bean 규약에 따라 setter, getter를 허용하고 있다. (변경가능) VO는 value(값)이다. 즉 변경불가능한 값이다. VO는 개발자가 추상화하여 정의한 상수이다. 넓은 범위가 아닌 개발자가 원하는 범위와 타입을 지정해서 쓴다.
-
안전한 프로그래밍(의도하지 않은 변경)을 위해서 DTO를 제외한 핵심로직은 모두 ImmutableObject로 만들 것
- 핵심로직(Model)에선 get과 set을 쓰지말아라.
데이터를 꺼낼 상황에 무조건 한번 의심해 보라. 반드시 꺼내야 하는지 아님 메시지를 보내서 처리할 수 있는지. ImmutableObject의 값을 변경할 일이 생긴다면, 메소드로 값을 변경한 새로운 객체를 생성하여라.
3-2 Builder 패턴
객체 생성을 위해 사용하는 디자인 패턴
- 객체 생성과 조립 방식을 분리하는 것이 목적
- 코드 읽기와 유지보수가 용의해진다.
생성자의 인자가 많을 경우 Builder 패턴 적용을 고려하라. https://cheese10yun.github.io/intellij-builder-pattern/ https://johngrib.github.io/wiki/builder-pattern/
기존의 생성자를 이용한 방식
필수 인자 외에 선택인자들을 받아 객체를 만들 때, 코드 수정이 어려움. 인자 수가 많아지면 인자의 위치가 어떤 필드인지 알기 힘들다.
public class Person {
private final String name; // 필수
// 선택
private int age;
private int height;
private String university;
private String[] hobby;
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, int age, int height) {
this.name = name;
this.age = age;
this.height = height;
}
public Person(String name, int age, int height, String university) {
this.name = name;
this.age = age;
this.height = height;
this.university = university;
}
public Person(String name, int age, int height, String university, String[] hobby) {
this.name = name;
this.age = age;
this.height = height;
this.university = university;
this.hobby = hobby;
}
}
Java Bean 방식
setter
메소드를 사용하여 어떤 인자를 추가하는지 알 수 있다. 그리고 생성자가 여러개일 필요가 없어서 좋다. 하지만 한번에 객체를 생성할 수 없고, 객체를 만들고 나서 계속 값을 추가해 주어야한다. ImmutableObject(변경 불가능한 객체)를 만들 때 쓸 수 없는 방식이다.
public class Person {
private String name;
private int age;
private int height;
private String university;
private String[] hobby;
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getUniversity() {
return university;
}
public void setUniversity(String university) {
this.university = university;
}
public String[] getHobby() {
return hobby;
}
public void setHobby(String[] hobby) {
this.hobby = hobby;
}
}
Builder 패턴을 쓸 경우
한번에 객체를 생성할 수 있다(chaining이 가능하기 때문에). 또한 인자를 추가하기 쉽고, 추가할 때 마다 인자가 어떤 인자인지 확실하게 구별할 수 있다. setter
메소드를 쓰지 않기때문에 ImmutableObject 만들 수 있다.
build()
를 호출할 때 값의 유효성 검사를 전담시킬 수 있다.
Person sehun = Person.PersonBuilder
.aPerson()
.withName("sehun")
.withAge(28)
.withHeight(178)
.withUniversity("SKHU")
.withHobby(new String[]{"sleep", "music"})
.build();
// inner builder (클래스 내부에 inner class로 빌더를 가질 경우)
public class Person {
private final String name;
private int age;
private int height;
private String university;
private String[] hobby;
public Person(String name) {
this.name = name;
}
public static final class PersonBuilder {
private String name;
private int age;
private int height;
private String university;
private String[] hobby;
private PersonBuilder() {
}
public static PersonBuilder aPerson() {
return new PersonBuilder();
}
public PersonBuilder withName(String name) {
this.name = name;
return this;
}
public PersonBuilder withAge(int age) {
this.age = age;
return this;
}
public PersonBuilder withHeight(int height) {
this.height = height;
return this;
}
public PersonBuilder withUniversity(String university) {
this.university = university;
return this;
}
public PersonBuilder withHobby(String[] hobby) {
this.hobby = hobby;
return this;
}
public Person build() {
Person person = new Person(name);
person.age = this.age;
person.height = this.height;
person.university = this.university;
person.hobby = this.hobby;
return person;
}
}
}