
이 저작물은 크리에이티브 커먼즈 저작자표시-동일조건변경허락 2.0 대한민국 라이선스에 따라 이용할 수 있습니다.
필드 자체 캡슐화를 실시해야 할 가장 절실할 시점은 상위클래스 안의 필드에 접근하되 이 변수 접근을 하위클래스에서 계산된 값으로 재정의해야 할때다. 먼저 필드를 자체 캡슐화한 후 필요할때 읽기 메서드와 쓰기 메서드를 재정의하면 된다.
package com.kws.eight;
public class IntRange {
private int _low,_high;
boolean includes(int arg){
return arg >= _low && arg <= _high;
}
void grow(int factor){
_high = _high * factor;
}
IntRange(int low, int high){
_low = low;
_high = high;
}
}
package com.kws.eight;
public class IntRange {
private int _low,_high;
boolean includes(int arg){
return arg >= get_low() && arg <= get_high();
}
void grow(int factor){
this._high = get_high() * factor;
}
public int get_low() {
return _low;
}
public void set_low(int _low) {
this._low = _low;
}
public int get_high() {
return _high;
}
public void set_high(int _high) {
this._high = _high;
}
}
자체 캡슐화를 실시할 때는 생성자 안에 쓰기 메서드를 사용할 때 주의해야 한다.대체로 객체가 생성된 후엔 속성을 변경하려고 쓰기 메서드를 사용하므로,쓰기 메서드에 초기화 시점과 다른 기능이 추가 됐을 수 있다고 전체할 때가 많다. 이럴땐 생성자나 별도의 초기화 메서드에서 직접 접근하게 하는 것이 좋다.
package com.kws.eight;
public class IntRange {
private int _low,_high;
IntRange(int low, int high){
initialize(low, high);
}
private void initialize(int low, int high){
this._low = low;
this._high = high;
}
boolean includes(int arg){
return arg >= get_low() && arg <= get_high();
}
void grow(int factor){
this._high = get_high() * factor;
}
public int get_low() {
return _low;
}
public void set_low(int _low) {
this._low = _low;
}
public int get_high() {
return _high;
}
public void set_high(int _high) {
this._high = _high;
}
}
생성자 초기화 메서드를 해두면 IntRange의 기능을 전부 재정의하면 기능을 하나도 수정하지 않고 cap을 계산에 넣을 수 있다.
package com.kws.eight;
public class CappedRange extends IntRange{
private int _cap;
CappedRange(int low, int high,int cap) {
super(low, high);
this._cap = cap;
}
public int get_cap() {
return _cap;
}
public void set_cap(int _cap) {
this._cap = _cap;
}
}
데이터 항목에 데이터나 기능을 더 추가해야 할때는 데이터 항목을 객체로 전환하자.
추가적인 데이터나 동작을 필요로 하는 데이터 아이템이 있을 때는 데이터 아이템을 객체로 바꾸어라.
public class OrderBefore {
private String customer;
public String getCustomer() {
return customer;
}
public void setCustomer(String customer) {
this.customer = customer;
}
private static int numberOrderFor(Collection orders, String customer){
int result = 0;
Iterator iter = orders.iterator();
while(iter.hasNext()){
OrderBefore each = (OrderBefore) iter.next();
if(each.getCustomer().equals(customer)){
result++;
}
}
return result;
}
}
public class Customer {
private final String name;
public Customer(String customer) {
this.name = customer;
}
public String getCustomers() {
return this.name;
}
}
public class OrderAfter {
private Customer _customer;
public OrderAfter(String arg) {
this._customer = new Customer(arg);
}
public String getCustomers(){
return _customer.getCustomers();
}
public void setCustomer(String customer){
_customer = new Customer(customer);
}
@SuppressWarnings("rawtypes")
private static int numberOrderFor(Collection orders, String customer){
int result = 0;
Iterator iter = orders.iterator();
while(iter.hasNext()){
OrderAfter each = (OrderAfter) iter.next();
if(each.getCustomers().equals(customer)){
result++;
}
}
return result;
}
}
클래스에 같은 인스턴스가 많이 들어 있어서 이것들을 하나의 객체로 바꿔야 할땐 그 객체를 참조 객체로 전환하자.
public class Customer {
private final String name;
public Customer(String name){
this.name = name;
}
public String getName() {
return name;
}
}
public class Order {
private Customer customer;
public Order(String customerName){
this.customer = new Customer(customerName);
}
public String getCustomer() {
return customer.getName();
}
public void setCustomer(String customerName) {
this.customer = new Customer(customerName);
}
@SuppressWarnings({ "unused", "rawtypes" })
private static int numberOrderFor(Collection orders, String customer){
int result = 0;
Iterator iter = orders.iterator();
while(iter.hasNext()){
Order each = (Order) iter.next();
if(each.getCustomer().equals(customer)){
result++;
}
}
return result;
}
}
public static Customer create(String name){
return new Customer(name);
}
그 다음, 생성자 호출을 팩토리 메서드 호출로 수정하자.
public Order(String customerName){
this.customer = Customer.create(customerName);
}
그 생성자 메서드를 private로 만들자
private Customer(String name){
this.name = name;
}
Customer에 한 개의 필드를 사용해서 라인 항목을 저장해 Customer클래스를 접근거점으로 만들겠다.
private static Dictionary _instances = new Hashtable();
그 다음 Customer인스턴스들을 요청이 있을 때 즉석에서 생성할지 아니면 미리 생성해 둘지 결졍해야 한다. 미리 생성해 두는 방식으로 하며, 시작 코드 안에 사용되는 Customer인스턴스들을 로딩하는 코드를 넣겠다.
static void loadCustomers(){
new Customer("우리 렌터카").store();
new Customer("커피 자판기").store();
new Customer("삼천리 가스").store();
}
@SuppressWarnings("unchecked")
private void store(){
_instances.put(this.getName(), this);
}
이제 팩토리 메서드를 수정해서 미리 생성해둔 Customer인스턴스를 반환하게 하자.
public static Customer create(String name){
// return new Customer(name);
// 미리 load된 객체반환
return ((Customer) _instances.get(name));
}
create 메서드는 반드시 미리 생성되어 있던 Customer인스턴스를 반환하므로 메서드명 변경을 실시해서 확인해야 한다.
public static Customer getNamed(String name){
return (Customer) _instances.get(name);
}
참조 객체가 작고 수정할 수 없고 관리하기 힘들 땐 그 참조 객체를 값 객체로 만들자.
public class Currency {
private String code;
public String getCode() {
return code;
}
private Currency(String code){
this.code = code;
}
public static void main(String[] args) {
System.out.println(new Currency("USD").equals(new Currency("USD"))); // false;
}
}
Currency 클래스에는 여러 인스턴스가 들어 있다. 생성자만 사용하는 것은 불가능하다.그래서 private인 것이다. 이것을 값 객체로 변환하려면 그 객체가 변경불가인지 확인해야 한다. 변경불가가 아니면 값이 변경 가능할 경우 끝없는 별칭 문제가 발생하므로 이렇게 수정하지 말어야 한다. 여기서 참조 객체는 변경 불가이므로 이제 다음과 같이 equals메서드를 정의해야 한다.
public boolean equals(Object arg){
if(!(arg instanceof Currency)) return false;
Currency other = (Currency)arg;
return (code.equals(other.code));
}
equals 메서드를 정의할 때 다음과 같이 hashCode 메서드도 정의해야 한다.
public int hashCode(){
return this.code.hashCode();
}
배열을 구성하는 특정 원소가 별의별 의미를 지닐땐 그 배열을 각 원소마다 필드가 하나씩 든 객체로 전환하자.
String[] row = new String[3];
row[0] = "Liverpool";
row[1] = "15";
String name = row[0];
int wins = Integer.parseInt(row[1]);
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
Performance Class
public class Performance {
private String name;
private String wings;
public String getName() {
return name;
}
public void setName(String arg) {
this.name = arg;
}
public int getWins(){
return Integer.parseInt(wings);
}
public void setWins(String arg){
this.wings = arg;
}
}
도메인 데이터는 GUI컨트롤 안에서만 사용 가능한데,도메인 메서드가 그 데이터에 접근해야 할 땐 그 데이터를 도메인 객체로 복사하고,양측의 데이터를 동기화하는 관측 인터페이스 observer를 작성하자.
계층구조가 체계적인 시스템은 비즈니스 로직 처리 코드와 사용자 인터페이스 처리 코드가 분리되어 있다.아래와 같은 몇가지 이유로…
public class IntervalWindow extends Frame{
TextField _startField;
TextField _endField;
TextField _lengthField;
class SymFocus extends FocusAdapter{
public void focusLost(FocusEvent event){
Object object = event.getSource();
if(object == _startField){
StartField_FocusLost(event);
}else if(object == _endField){
EndField_FocusLost(event);
}else if(object == _lengthField){
LengthField_FocusLost(event);
}
}
}
private boolean isNotInteger(String text) {
boolean result = false;
try{
Integer.parseInt(text);
result = true;
}catch(Exception e){
}
return result;
}
void StartField_FocusLost(FocusEvent event){
if(isNotInteger(_startField.getText())){
_startField.setText("0");
}
calculateLength();
}
void EndField_FocusLost(FocusEvent event){
if(isNotInteger(_endField.getText())){
_endField.setText("0");
}
calculateLength();
}
void LengthField_FocusLost(FocusEvent event){
if(isNotInteger(_lengthField.getText())){
_lengthField.setText("0");
}
calculateEnd();
}
void calculateLength(){
try{
int start = Integer.parseInt(_startField.getText());
int end = Integer.parseInt(_endField.getText());
int length = end - start;
_lengthField.setText(String.valueOf(length));
}catch(NumberFormatException e){
throw new RuntimeException("잘못된 숫자 형식 에러");
}
}
void calculateEnd(){
try{
int start = Integer.parseInt(_startField.getText());
int length = Integer.parseInt(_endField.getText());
int end = start + length;
_lengthField.setText(String.valueOf(end));
}catch(NumberFormatException e){
throw new RuntimeException("잘못된 숫자 형식 에러");
}
}
}
여기서 할 일은 GUI코드와 로직코드를 분리하는 것이다. calculateLength 메서드와 calculateEnd 메서드를 별도의 도메인 클래스로 옮겨야 한다. 그러려면 start,end,length 변수를 IntervalWindow 클래스를 거치지 않고 참조해야 한다.그러기 위한 유일한 방법은 start,end,length 변수 데이터를 도메인 클래스로 복사하고 그 데이터를 GUI클래스의 데이터와 동기화하는 것이다.이 작업을 소위 관측 데이터 복제라고 한다.
public class Interval extends Observable{}
class IntervalWindow....{
private Interval _subject;
}
public IntervalWindowChange(){
_subject = new Interval();
_subject.addObserver(this);
update(_subject, null);
}
public class IntervalWindowChange extends Frame implements Observer{}
@Override
public void update(Observable o, Object arg) {
}
public String getEnd(){
return _endField.getText();
}
public void setEnd(String arg){
_endField.setText(arg);
}
void calculateLength(){
try{
int start = Integer.parseInt(_startField.getText());
int end = Integer.parseInt(getEnd());
int length = end - start;
_lengthField.setText(String.valueOf(length));
}catch(NumberFormatException e){
throw new RuntimeException("잘못된 숫자 형식 에러");
}
}
void calculateEnd(){
try{
int start = Integer.parseInt(_startField.getText());
int length = Integer.parseInt(_lengthField.getText());
int end = start + length;
setEnd(String.valueOf(end));
}catch(NumberFormatException e){
throw new RuntimeException("잘못된 숫자 형식 에러");
}
}
void EndField_FocusLost(FocusEvent event){
setEnd(_endField.getText());
if(isNotInteger(getEnd())){
setEnd("0");
}
calculateLength();
}
private String end = "0";
void setEnd(String arg){
this.end = arg;
setChanged();
notifyObservers();
}
String getStart(){
return start;
}
public String getEnd(){
return _subject.getEnd();
}
public void setEnd(String arg){
_subject.setEnd(arg);
}
@Override
public void update(Observable o, Object arg) {
_endField.setText(_subject.getEnd());
}
두 클래스가 서로의 기능을 사용해야 하는데 한 방향으로만 연결되어 있을땐 역 포인터를 추가하고 두 클래스를 모두 업데이트할 수 있게 접근 한정자를 수정하자.
class Order...
Customer getCustomer(){
return customer;
}
void setCustomer(Customer arg){
customer = arg;
}
class Customer{
private Set orders = new HashSet();
...
}
두 클래스가 양방향으로 연결되어 있는 한 클래스가 다른 클래스의 기능을 더 이상 사용하지 않게 됐을 땐 불필요한 방향의 연결을 끊자.
양방향 연결로 인해 두 클래스는 서로 종속된다, 한 클래스를 수정하면 다른 클래스도 변경된다.종속성이 많으면 시스템의 결합력이 사소한 수정에도 예기치 못한 각종 문제가 발생한다.
public class Order {
private Customer customers;
public Customer getCustomer() {
return customers;
}
public void setCustomer(Customer arg) {
// this.customer = customer;
if (customers != null)
customers.friendOrders().remove(this);
customers = arg;
if (customers != null)
customers.friendOrders().add(this);
}
}
public class Customer {
private static Set orders = new HashSet();
void addOrder(Order arg){
arg.setCustomer(this);
}
Set friendOrders(){
return orders;
}
}
Order...
double getDiscountedPrice(Customer customer) {
return getGrossPrice() * (1 - customer.getDiscount());
}
Customer...
double getPriceFor(Order order){
return order.getDiscountedPrice(this);
}
특수 의미를 지닌 리터럴 숫자가 있을 땐 의미를 살린 이름의 상수를 작성한 후 리터럴 숫자를 그 상수로 교체하자.
double potentiaEnergy(double mass, double height){
return mass * 9.81 * height;
}
static final double GRAVITATION_CONSTANT = 9.81;
double potentiaEnergys(double mass, double height){
return mass * GRAVITATION_CONSTANT * height;
}
public String name;
--------------------------------------
private String name;
public String getName(){return name;}
public void setName(String arg){name = arg;}
메서드가 컬렉션을 반환할때 그 메서드가 읽기전용 뷰를 반환하게 수정하고 추가메서드와 삭제메서드를 작성하자.
컬렉션은 다른 종류의 데이터와는 약간 다른 읽기/쓰기 방식을 사용해야 한다. 읽기 메서드는 컬렉션 객체 자체를 반환해선 안된다. 왜냐하면 컬렉션 참조 부분이 컬렉션의 내용을 조작해도 그 컬렉션이 든 클래스는 무슨 일이 일어나는지 모르기 때문이다.이로 인해 컬렉션 참조 코드에서 그 객체의 데이터 구조가 지나치게 노출된다.값이 여러 개인 속성을 읽는 읽기 메서드는 컬렉션 조작이 불가능한 형식을 반환하고 불필요하게 자세한 컬렉션 구조 정보는 감춰야 한다. 컬렉션 쓰기 메서드는 절대 있으면 안되므로, 원소를 추가하는 메서드와 삭제하는 메서드를 대신 사용해야 한다.
package collection;
public class Course {
private String name;
private boolean isA;
public Course(String name, boolean isAdvanced){
this.name = name;
this.isA = isAdvanced;
}
public boolean isAdvanced(){
return true;
}
}
package collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import junit.framework.Assert;
public class Person {
// private Set course;
@SuppressWarnings("rawtypes")
private Set course = new HashSet();
public Set getCourse() {
return course;
}
// @SuppressWarnings("unchecked")
// public Set getCourse() {
// return Collections.unmodifiableSet(course);
// unmodifiableSet 메소드는 지정된 세트의 변경 불가능한 뷰를 돌려줍니다.
// }
// 초기화하려고 원소를 추가할때 추가 기능이 확실히 필요 없다면 다음과 같이 루프를 삭제하고 addAll메서드를 사용.
// public void setCourse(Set course) {
// this.course = course;
// }
@SuppressWarnings("rawtypes")
public void initalizeCourse(Set arg) {
// Assert.isTrue(course.isEmpty());
Iterator iter = arg.iterator();
while(iter.hasNext()){
addCourse((Course) iter.next());
}
}
// public void initalizeCourse(Set arg) {
// Assert.isTrue(course.isEmpty());
// course.addAll(arg);
// }
@SuppressWarnings("unchecked")
public void addCourse(Course arg){
course.add(arg);
}
public void removeCourse(Course arg){
course.remove(arg);
}
@SuppressWarnings("rawtypes")
int numberOfAdvancedCourse(Person person){
Iterator iter = person.getCourse().iterator();
int count = 0;
while(iter.hasNext()){
Course each = (Course) iter.next();
if(each.isAdvanced()){
count++;
}
}
return count;
}
int numberOfCourse(){
return course.size();
}
}
package collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.junit.Assert;
public class Client {
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) {
Person kent = new Person();
Set s = new HashSet();
// before
// s.add(new Course("스몰토크 프로그래밍", false));
// s.add(new Course("싱글몰드 위스키 음미하기", true));
// after
kent.addCourse(new Course("스몰토크 프로그래밍", false));
kent.addCourse(new Course("싱글몰드 위스키 음미하기", true));
// kent.setCourse(s);
kent.initalizeCourse(s);
// Assert.assertEquals(2, kent.getCourse().size());
Course refact = new Course("리팩토링", true);
kent.getCourse().add(refact);
// before
// kent.getCourse().add(new Course("지독한 빈정거림", false));
// after
kent.addCourse(new Course("지독한 빈정거림", false));
// Assert.assertEquals(4, kent.getCourse().size());
kent.getCourse().remove(refact);
// Assert.assertEquals(3, kent.getCourse().size());
// System.out.println(kent.getCourse().size());
System.out.println(kent.getCourse());
//고급과정
Iterator iter = kent.getCourse().iterator();
int count = 0;
while(iter.hasNext()){
Course each = (Course) iter.next();
if(each.isAdvanced()){
count++;
}
}
System.out.println(count);
}
}
package collection;
import java.util.Enumeration;
import java.util.Vector;
public class PersonVecter {
private Vector course;
public Vector getCourse() {
return course;
}
public void setCourse(Vector course) {
this.course = course;
}
@SuppressWarnings("unchecked")
public void addCourse(Course arg){
course.addElement(arg);
}
public void removeCourse(Course arg){
course.removeElement(arg);
}
@SuppressWarnings("rawtypes")
public void initalizeCourse(Vector arg) {
// Assert.isTrue(course.isEmpty());
Enumeration iter = arg.elements();
while(iter.hasMoreElements()){
addCourse((Course) iter.nextElement());
}
}
}
package collection;
public class ArrayTest {
private String[] skills;
// public String[] getSkills() {
// return skills;
// }
public String[] getSkills() {
String[] result = new String[skills.length];
System.arraycopy(skills, 0, result, 0, skills.length);
return result;
}
// public void setSkills(String[] arg) {
// this.skills = skills;
// }
public void setSkill(int index, String skill) {
skills = new String[5];
skills[index] = skill;
}
public void setSkills(String[] arg) {
skills = new String[skills.length];
for(int i = 0; i < skills.length; i++){
setSkill(i,arg[i]);
}
}
}
전통적인 프로그래밍 환경에서의 레코드 구조에 대한 인터페이스가 필요한 경우, 그 레코드를 위한 데이터 객체를 만들어라.
기능에 영향을 숫자형 분류 부호가 든 클래스가 있을 땐 그 숫자를 새 클래스로 바꾸자.
class Person{
public static final int O = 0;
public static final int A = 1;
public static final int B = 2;
public static final int AB = 3;
private int bloodGroup;
public Person(int bloodGroup){
this.bloodGroup = bloodGroup;
}
public void setBloodGroup(int arg){
this.bloodGroup = arg;
}
public int getBloodGroup(){
return bloodGroup
}
}
package replace;
public class Person {
private BloodGroup bloodgroup;
public static final int O = BloodGroup.O.getCode();
public static final int A = BloodGroup.A.getCode();
public static final int B = BloodGroup.B.getCode();
public static final int AB = BloodGroup.AB.getCode();
// private int bloodGroup;
public Person(BloodGroup arg){
// this.bloodGroup = arg;
this.bloodgroup = arg;
}
public BloodGroup getBloodGroup() {
// return bloodGroup;
return bloodgroup;
}
public void setBloodGroup(BloodGroup arg) {
this.bloodgroup = arg;
}
public static void main(String[] args) {
// Person thePerson = new Person(Person.A);
Person thePerson = new Person(BloodGroup.A);
thePerson.getBloodGroup().getCode();
// thePerson.setBloodGroup(Person.AB);
thePerson.setBloodGroup(BloodGroup.AB);
}
}
package replace;
public class BloodGroup {
public static final BloodGroup O = new BloodGroup(0);
public static final BloodGroup A = new BloodGroup(1);
public static final BloodGroup B = new BloodGroup(2);
public static final BloodGroup AB = new BloodGroup(3);
private static final BloodGroup[] values = {O ,A ,B ,AB};
private final int code;
private BloodGroup(int arg){
this.code = arg;
}
public int getCode() {
return code;
}
public static BloodGroup code(int arg){
return values[arg];
}
}
클래스 기능에 영향을 주는 변경불가 분류 부호가 있을 땐 분류 부호를 하위클래스로 만들자
분류 부호를 하위클래스로 전환 기법은 주로 조건문을 재정의로 전환을 가능하게 하는 사전 작업으로 시행할 때가 많다. 분류 부호를 하위클래스로 전환기법은 조건문이 있으면 실시한다.
특정 분류 부호의 객체에만 관련된 기능이 있을 때도 분류 부호를 하위클래스로 전환 기법을 적용해야 한다.
package subclass;
abstract class Employee {
private int type;
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;
static Employee create(int arg) {
switch (arg) {
case ENGINEER:
return new Engineer(0);
case SALESMAN:
// return new Salesman();
case MANAGER:
// return new Manage();
default:
throw new IllegalArgumentException("Incorrect");
}
}
public Employee(int arg) {
this.type = arg;
}
// public int getType() {
// return type;
// }
abstract int getType();
public void setType(int type) {
this.type = type;
}
}
package subclass;
public class Engineer extends Employee{
public Engineer(int arg) {
super(arg);
}
public int getType(){
return Employee.ENGINEER;
}
}
분류 부호가 클래스의 기능에 영향을 주지만 하위클래스로 전환할 수 없을 땐 그 분류 부호를 상태 객체로 만들자.
public class Employee {
private EmployeeType type;
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;
private int monthlySalary = 0;
private int commission = 0;
private int bonus = 0;
public Employee(EmployeeType arg) {
this.type = arg;
}
public void setType(int arg) {
this.type = type;
}
}
abstract class EmployeeType {
abstract int getTypeCode();
}
public class Engineer extends EmployeeType{
int getTypeCode() {
return Employee.ENGINEER;
}
}
public class Manager extends EmployeeType{
int getTypeCode() {
return Employee.MANAGER;
}
}
int getTypeCode() {
return Employee.SALESMAN;
}
}
private EmployeeType type;
int getType(){
return type.getTypeCode();
}
public void setType(int arg) {
// this.type = type;
switch (arg) {
case ENGINEER:
type = new Engineer();
break;
case SALESMAN:
type = new Salesman();
break;
case MANAGER:
type = new Manager();
break;
default:
throw new IllegalArgumentException("사원 부호가 잘못됨.");
}
type = EmployeeType.newType(arg);
}
Employee...
public void setType(int arg) {
type = EmployeeType.newType(arg);
}
EmployeeType...
static EmployeeType newType(int code){
switch (code) {
case ENGINEER:
return new Engineer();
case SALESMAN:
return new Salesman();
case MANAGER:
return new Manager();
default:
throw new IllegalArgumentException("사원 부호가 잘못됨.");
}
}
Employee...
int payAmount(int arg) {
switch (arg) {
case EmployeeType.ENGINEER:
return monthlySalary;
case EmployeeType.SALESMAN:
return monthlySalary + commission;
case EmployeeType.MANAGER:
return monthlySalary + bonus;
default:
throw new IllegalArgumentException("Incorrect");
}
}
여러 하위클래스가 상수 데이터를 반환하는 메서드만 다른땐 각 하위클래스의 메서드를 상위클래스 필드로 전환하고 하위클래스는 전부 삭제하라.
기능을 추가하거나, 동작의 변화를 허용하기 위해서 서브클래스를 만든다. 동작 변화의 한 형태는 상수메소드(하드 코딩된 값을 리턴하는 메소드)이다. 상수메소드는 유용하기는 하지만, 상수 메소드를 포함하고 있는 서브클래스는 존재할 가치가 있을 만큼 충분한 일을 하는 것이 아니다. 서브클래스를 사용하면서 생기는 여러 복잡성을 제거할 수 있다.
abstract class Person {
abstract boolean isMale();
abstract char getCode();
}
public class Maie extends Person{
@Override
boolean isMale() {
return true;
}
@Override
char getCode() {
return 'M';
}
}
public class Female extends Person{
@Override
boolean isMale() {
return false;
}
@Override
char getCode() {
return 'F';
}
}
static Person createMale(){
return new Person(true,'M');
}
static Person createFemale(){
return new Person(false,'F');
}
private final boolean _isMale;
private final char _code;