[Java 기초문법] by Professional Java Developer Career Starter: Java Foundations @ Udemy
More Advanced OOP Topics
시작
데이터를 regex로 파싱해서 정보를 뽑아내는 다음과 같은 코드가 있다고 치자.
package com.neutrio.employee;
import java.text.NumberFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String peopleText = """
Flinstone, Fred, 1/1/1990, Programmer, {locpd=2000,yoe=10,iq=140}
Flinstone2, Fred2, 1/1/1990, Programmer, {locpd=1300,yoe=14,iq=100}
Flinstone3, Fred3, 1/1/1990, Programmer, {locpd=2300,yoe=8,iq=105}
Flinstone4, Fred4, 1/1/1990, Programmer, {locpd=1630,yoe=3,iq=115}
Flinstone5, Fred5, 1/1/1990, Programmer, {locpd=5,yoe=10,iq=100}
Rubble, Barney, 2/2/1905, Manage, {orgSize=300,dr=10}
Rubble2, Barney2, 2/2/1905, Manage, {orgSize=100,dr=4}
Rubble3, Barney3, 2/2/1905, Manage, {orgSize=200,dr=2}
Rubble4, Barney4, 2/2/1905, Manage, {orgSize=500,dr=8}
Rubble5, Barney5, 2/2/1905, Manage, {orgSize=175,dr=20}
Flinstone, Wilma, 3/3/1910, Analyst, {projectCount=3}
Flinstone2, Wilma2, 3/3/1910, Analyst, {projectCount=4}
Flinstone3, Wilma3, 3/3/1910, Analyst, {projectCount=5}
Flinstone4, Wilma4, 3/3/1910, Analyst, {projectCount=6}
Flinstone5, Wilma5, 3/3/1910, Analyst, {projectCount=9}
Rubble, Betty, 4/4/1915, CEO, {avgStockPrice=300}
""";
String peopleRegex = "(?<lastName>\\w+),\\s*(?<firstName>\\w+),\\s*(?<dob>\\d{1,2}/\\d{1,2}/\\d{4}),\\s*(?<role>\\w+)(?:,\\s*\\{(?<details>.*)\\})?\\n";
Pattern peoplePat = Pattern.compile(peopleRegex);
Matcher peopleMat = peoplePat.matcher(peopleText);
String progRegex = "\\w+\\=(?<locpd>\\w+)\\,\\w+\\=(?<yoe>\\w+)\\,\\w+\\=(?<iq>\\w+)";
Pattern coderPat = Pattern.compile(progRegex);
String mgrRegex = "\\w+=(?<orgSize>\\w+),\\w+=(?<dr>\\w+)";
Pattern mgrPat = Pattern.compile(mgrRegex);
String analystRegex = "\\w+=(?<projectCount>\\w+)";
Pattern analystPat = Pattern.compile(analystRegex);
String ceoRegex = "\\w+=(?<avgStockPrice>\\w+)";
Pattern ceoPat = Pattern.compile(ceoRegex);
int totalSalaries = 0;
while (peopleMat.find()) {
totalSalaries += switch (peopleMat.group("role")){
case "Programmer" -> {
String details = peopleMat.group("details");
Matcher coderMat = coderPat.matcher(details);
int salary = 0;
if (coderMat.find()){
int locpd = Integer.parseInt(coderMat.group("locpd"));
int yoe = Integer.parseInt(coderMat.group("yoe"));
int iq = Integer.parseInt(coderMat.group("iq"));
// System.out.printf("Programmer loc: %s yoe: %s iq: %s%n", locpd, yoe, iq);
salary = 3000 + locpd * yoe * iq;
} else{
salary = 3000;
}
yield salary;
}
case "Manager" -> {
String details = peopleMat.group("details");
Matcher mgrMat = mgrPat.matcher(details);
int salary = 0;
if (mgrMat.find()){
int orgSize = Integer.parseInt(mgrMat.group("orgSize"));
int directReports = Integer.parseInt(mgrMat.group("dr"));
salary = 3500 + orgSize * directReports;
} else{
salary = 3500;
}
yield salary;
}
case "Analyst" -> {
String details = peopleMat.group("details");
Matcher analystMat = analystPat.matcher(details);
int salary = 0;
if (analystMat.find()){
int projectCount = Integer.parseInt(analystMat.group("projectCount"));
salary = 2500 + projectCount * 2;
} else{
salary = 2500;
}
yield salary;
}
case "CEO" -> {
String details = peopleMat.group("details");
Matcher ceoMat = ceoPat.matcher(details);
int salary = 0;
if (ceoMat.find()){
int avgStockPrice = Integer.parseInt(ceoMat.group("avgStockPrice"));
salary = 5000 * avgStockPrice;
} else{
salary = 5000;
}
yield salary;
}
default -> 0;
};
}
NumberFormat currencyInstance = NumberFormat.getCurrencyInstance();
System.out.printf("The total payout should be %s%n", currencyInstance.format(totalSalaries));
}
}
There's lots and lots of programmers who probabliy don't really see anything wrong with this code as alog as it works.
Just getting the code to work doesn't really mean you're done.
1) first off, this is completely procedural.
2) there are lots of duplicated blocks
not taking advantage of hardly any of the benefits of Java's object oriented capabilities this implementation.
정말 지루하고 재미없는 절차지향적인 코드.
이제 뜯어 고쳐보자.
먼저 생각해볼 수 있는 것은 class를 고려해보는 것
class by role => 4
now constructior에 어떤 변수를 입력받을 것인가?
여기서 전체 스트링을 받고 있기 때문에
Programmer programmer = new Programmer(peopleMat.group());
손쉽게 전체 정보를 입력받을 수 있다.
Field에 변수를 지정해주고 method에서 this로 설정
처음 받아오는 regex는 바뀔 필요가 없으니
private final String peopleRegex
세부 정보 사항까지 가져온다.
private LocalDate dob; // 여기서 date를 string으로 받을 수도 있지만 가급적 실제와 같게 한다면 숫자 형태로 받을 수도 있다
private int linesOfCode = 0;
private int yearsOfExp = 0;
private int iq =0;
private final String peopleRegex = "(?<lastName>\\w+),\\s*(?<firstName>\\w+),\\s*(?<dob>\\d{1,2}/\\d{1,2}/\\d{4}),\\s*(?<role>\\w+)(?:,\\s*\\{(?<details>.*)\\})?\\n";
private final Pattern peoplePat = Pattern.compile(peopleRegex);
private final String progRegex = "\\w+\\=(?<locpd>\\w+)\\,\\w+\\=(?<yoe>\\w+)\\,\\w+\\=(?<iq>\\w+)";
private final Pattern coderPat = Pattern.compile(progRegex);
세부 사항을 메소드 내에서 정의해준다.
public Programmer(String personText) {
Matcher peopleMat = peoplePat.matcher(personText);
if (peopleMat.find()){
this.lastName = peopleMat.group("lastName");
this.firstName = peopleMat.group("firstName");
this.dob = LocalDate.from(dfFormatter.parse(peopleMat.group("dob")));
Matcher progMat = progPat.matcher(peopleMat.group("details"));
if (progMat.find()){
this.linesOfCode = Integer.parseInt(progMat.group("locpd"));
this.linesOfCode = Integer.parseInt(progMat.group("yoe"));
this.linesOfCode = Integer.parseInt(progMat.group("iq"));
}
}
합계 구하는 메소드 구현해주기
본 클래스에서 길었던 부분을 이렇게 줄일 수 있다.
직업별 클래스로 전부 넣어주고 정리한 결과
package com.neutrio.employee;
import java.text.NumberFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String peopleText = """
Flinstone, Fred, 1/1/1990, Programmer, {locpd=2000,yoe=10,iq=140}
Flinstone2, Fred2, 1/1/1990, Programmer, {locpd=1300,yoe=14,iq=100}
Flinstone3, Fred3, 1/1/1990, Programmer, {locpd=2300,yoe=8,iq=105}
Flinstone4, Fred4, 1/1/1990, Programmer, {locpd=1630,yoe=3,iq=115}
Flinstone5, Fred5, 1/1/1990, Programmer, {locpd=5,yoe=10,iq=100}
Rubble, Barney, 2/2/1905, Manage, {orgSize=300,dr=10}
Rubble2, Barney2, 2/2/1905, Manage, {orgSize=100,dr=4}
Rubble3, Barney3, 2/2/1905, Manage, {orgSize=200,dr=2}
Rubble4, Barney4, 2/2/1905, Manage, {orgSize=500,dr=8}
Rubble5, Barney5, 2/2/1905, Manage, {orgSize=175,dr=20}
Flinstone, Wilma, 3/3/1910, Analyst, {projectCount=3}
Flinstone2, Wilma2, 3/3/1910, Analyst, {projectCount=4}
Flinstone3, Wilma3, 3/3/1910, Analyst, {projectCount=5}
Flinstone4, Wilma4, 3/3/1910, Analyst, {projectCount=6}
Flinstone5, Wilma5, 3/3/1910, Analyst, {projectCount=9}
Rubble, Betty, 4/4/1915, CEO, {avgStockPrice=300}
""";
String peopleRegex = "(?<lastName>\\w+),\\s*(?<firstName>\\w+),\\s*(?<dob>\\d{1,2}/\\d{1,2}/\\d{4}),\\s*(?<role>\\w+)(?:,\\s*\\{(?<details>.*)\\})?\\n";
Pattern peoplePat = Pattern.compile(peopleRegex);
Matcher peopleMat = peoplePat.matcher(peopleText);
int totalSalaries = 0;
while (peopleMat.find()) {
totalSalaries += switch (peopleMat.group("role")){
case "Programmer" -> {
Programmer programmer = new Programmer(peopleMat.group());
System.out.println(programmer.toString());
yield programmer.getSalary();
}
case "Manager" -> {
Manager manager = new Manager(peopleMat.group());
System.out.println(manager.toString());
yield manager.getSalary();
}
case "Analyst" -> {
Analyst analyst = new Analyst(peopleMat.group());
System.out.println(analyst.toString());
yield analyst.getSalary();
}
case "CEO" -> {
CEO ceo = new CEO(peopleMat.group());
System.out.println(ceo.toString());
yield ceo.getSalary();
}
default -> 0;
};
}
NumberFormat currencyInstance = NumberFormat.getCurrencyInstance();
System.out.printf("The total payout should be %s%n", currencyInstance.format(totalSalaries));
}
}
Interfaces
They are almost like hollowed out skeletons of a class.
Traditionally describe some or all of the public methods of a class. When a class has a relationship to an interface, that class is said to implement that interface.
=> Abstraction
인터페이스를 비유하자면 outlet이다.
It interfaces your devices that you want to plug in to the electricity.
It also serves to abstract way some of the complexity of interacting with the electricity because you certainly could just have the bare wires that are behind that oulet, but that would be a level of implementation detail that most people would not want to get into.
인터페이스를 도입해서 코드를 고쳐보자.
When you identify methods that can be in common btw multiple classes, that can be a sign => abstract those class BEHIND interfaces.
예를 들어 이 코드에서 계속 반복되는 toString과 getSalaray가 있다.
employee라는 이름의
인터페이스 클라스를 새로 추가한다.
여기서 다뤄줄 것은 getSalary 메소드
여기서 public은 따로 필요 없는데 interface by it self defines all of the public methods. 인터페이스로서 define되는 클래스는 어차피 public으로 인식 될것이기 때문이다.
직업 클래스가 employee를 implements한다는 것을 아래와 같이 구현
main 클래스에서 직업을 묶어줄 수 있다.
일종의 Type이 지정되는 것과 같다
만약 Employee 인터페이스에 getSalary가 없다면 여기서 오류가 발생할 것이다.
직업 클래스에 getSalary가 있더라도 오류가 발생하는데 그 이유는 interface가 일종의 masking을 하기 때문
yield를 하나로 묶어주기 위해 먼저 employee를 initiate하고 switch를 넣어준다.
이때 더이상 culry braces가 필요하지 않아진다.
any one of these are now caputred => employee
각각이 same employee에 implement하고 있고, employee 내부에서 get salary를 하고 있기 때문에
한번에 호출하는 것이 가능하다.
Now these two code here only care about two things, that are 1) that employee contains toString method, 2) that employee contains getSalary method.
So above classes' details of what the underlying classes are unimportant to these code, and that allows us to abstract those details away and just get right down to the part that we actually care about, => get salary.
만약 코드가 복붙으로 구현이 되고 있다면 chances are you need to implement interface.
Super class
먼저 interface를 class로 바꾸면 오류가 뜨고
일반 클래스는 자체에서 impletmentation을 구현해야 하기 때문에 세미콜론으로 끝내지 못하므로
리턴값을 준다.
이때 직업 클래스에 구현한 implementation을 extends로 바꾼다.
이렇게 함으로써 인터페이스를 도입하는 것이 아니라 일반적인 class를 상속하는 형식으로 바귄다,
반복되는 fields를 pull up 해줌으로써 hierarchy를 형성할 수 있다.
Refactor를 누르면 이들이 사라지고 employee로 이동된다.
이미 pull 업이 되었기 때문에 다른 직업 클래스의 같은 항목들은 제거해줄 수 있다.
Constructor도 pull up 해보자.
아래와 같은 코드가 이제 pull up 되었기 때문에 단순 call up을 해주면 된다.
by calling super.
=> take that string = personText = from higher class = Employee
= > and parse to get last name, first name, dob
=> and set them on the instances.
여기서 프로그래머가 peopleMat을 사용하는데 pull up 되었고 받아오지 못하는 문제가 있다.
Employee 클래스에서 이를 field로 올려줌으로써
so that subclasses now will be able to access this peopleMat and related object as well.
접근할 수 있도록 protected로 전환
package com.neutrio.employees;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Programmer extends Employee {
private int linesOfCode = 0;
private int yearsOfExp = 0;
private int iq =0;
private final String progRegex = "\\w+\\=(?<locpd>\\w+)\\,\\w+\\=(?<yoe>\\w+)\\,\\w+\\=(?<iq>\\w+)";
private final Pattern progPat = Pattern.compile(progRegex);
public Programmer(String personText) {
super(personText);
Matcher progMat = progPat.matcher(peopleMat.group("details"));
if (progMat.find()){
this.linesOfCode = Integer.parseInt(progMat.group("locpd"));
this.yearsOfExp = Integer.parseInt(progMat.group("yoe"));
this.iq = Integer.parseInt(progMat.group("iq"));
}
}
public int getSalary(){
return 3000 + linesOfCode * yearsOfExp * iq;
}
}
So now what's left in theses subclasses is ideally just the fields and code that are unique to that particular subclass.
interface vs superclass
interface = don't have any data in common each other : intend to don't share any implementation details for the moethods.
supercalss = do share data or implementation details.
자바에서 extends는 하나만 허용한다. = not allow multiple inheritance.
but allows multiple interfaces
null 처리해보기
여기서 임시적으로 설정한 null은 match가 발생하지 않으면 null값을 리턴하게 될 것인데
그러면 calculation에서 문제가 생기면서 에러를 발생시킬 것이다
=> 잠재적 오류
이때 간단히 해결할 수 있는 것은
Employee에서 0을 리턴하고 있기 때문에
이런방법이 가능하다.
또 하나는 먼저 null을 체크하고 조건으로 처리하는 것이다.
하지만 조건문을 사용함으로써 코드가 복잡해지는 문제가 있다.
Abstract classes
샐러리에서 보너스를 계산하려면 어떻게 해야 할까?
employee를 abstract로 선언해보자.
여기서 했던 null 처리가 불가능해지는데
reason is because , which means abstract class is a class that is mean for you to create instances of it. it is meant only to serve as a super class for other class.
not for standalone object instances on its own.
그렇다면 abstract는 무슨 use of ?
when I don't want to implement it in my super class. rather I want to make sure that subclasses actually provide an implementation for that method.
Then abstract be declared
and a method to be abstract as well.
어떤면에서는 interface랑 비슷하다.
=> I have this class here that can do real work with real methods, and that I don't want to provide a concrete implementation at here.
따라서 이때 subclass에서 재정의해주지 않으면 위와 같은 에러가 발생
here subclass is FORCED to implement the get salary method.
여기서 유용한 점은 we can still refer to the method that is declared as abstract in higher class, in other places as if it's already implemented.
이때 tostring에서 돌아가는 로직은
get salary method가 future 이벤트에 depend on 하는데
that is this class doens't even necessarily know anything about =>
이럼으로써 역할에 대한 분리가 가능해진다는 점이다.
get bouns 도 마찬가지.
=> creating a template and prjoecting future event which should be implemented in concrete method which actually using it.
이러한 방식을 the template method pattern이라고 한다.
Factory Methods
메인 클래스에서 이 부분은 최선일까?
이 클래스에서 하고자하는 것이
1) 데이터를 파싱해서
2) get salary해서
3) sum 정보를 뽑아낸다.
are there any unnecessary codes that are deterring the main purposes.
where might be a better place to put a code like this.
while (peopleMat.find()) {
employee = switch (peopleMat.group("role")){
case "Programmer" -> new Programmer(peopleMat.group());
case "Manager" -> new Manager(peopleMat.group());
case "Analyst" -> new Analyst(peopleMat.group());
case "CEO" -> new CEO(peopleMat.group());
main class really doesn't have to care how we get those salaries calculated.
factory method를 이용해보자.
employee 클래스에 생성
이때 static을 선언함으로써 이전에 field의 항목들도 static으로 맞춰줘야 하는데
변수가 겹쳐져서 문제가 생기므로 local에 별도로 선언해주기
public static final Employee createEmployee(String employeeText){
Matcher peopleMat = Employee.peoplePat.matcher(employeeText);
if (peopleMat.find()) {
return switch (peopleMat.group("role")){
case "Programmer" -> new Programmer(employeeText);
case "Manager" -> new Manager(employeeText);
case "Analyst" -> new Analyst(employeeText);
case "CEO" -> new CEO(employeeText);
default -> null;
};
} else{
return null;
}
}
What this method will parse is =>
Flinstone, Fred, 1/1/1990, Programmer, {locpd=2000,yoe=10,iq=140}
메인에서 넘겨주는 코드를 작성한다.
while (peopleMat.find()) {
employee = Employee.createEmployee(peopleMat.group());
System.out.println(employee.toString());
totalSalaries += employee.getSalary();
}
Nested class
Employee안에 extend Employee를 하는 nested class를 만들어서 null처리를 하는 법
이때 no arg default가 없다는 에러 메시지 발생
Employee가 한번 더 호출 될 때 다른 field가 문제될 수 있다.
이를 해결하는 방법은 간단히 null 처리를 해주는 것
이제 factory method에서 null 대신 없는 사람으로서 만든 함수를 호출할 수 있게 되었다.
Inner class
private static final class DummyEmployee extends Employee{
여기서 static을 빼면 inner class라고 한다.
if you don't need anything more than what you would get from a static nested class, and if you may need to refer to that static nested class in more than one place, then it may be advised to ge ahead and make it static.
difference => is the fact that since a static nested class is static, it is not allowed to access fields of its enclosing lcass.
becuase fields of a class only exist once you create an instance of that class, however static member of a class exist prior to during and after the creation of instances of that class. => missmatch
anonymous class
Here new class is declared that doesn't have a name. And simultaneously created one instance of this new class right here.
=> both do the declaration and the instatantiation all in one.
'Programming > Java, Spring' 카테고리의 다른 글
[Java 기초문법] 자바 람다 Lambdas, delegate method (0) | 2022.10.20 |
---|---|
[Java 기초문법] 자바 records (0) | 2022.10.20 |
[Java 기초문법] 객체지향 | Gradle, Enums, Enum의 여러 기능들 (0) | 2022.10.19 |
[Java 기초문법] 자바 코드 디버깅하기 (0) | 2022.10.19 |
[Java 기초문법] Testing code | junit을 이용한 테스팅, Edge case, Annuity 계산해보기, TDD로 guessing game implementing (0) | 2022.10.19 |