코드에서 스위치를 사용하지 않는 방법은 무엇입니까?
코드에서 스위치를 사용하지 않는 방법은 무엇입니까?
답변:
스위치 문은 그 자체로 반 패턴이 아니지만 객체 지향을 코딩 하는 경우 스위치 문을 사용하는 대신 다형성 으로 스위치 사용을 더 잘 해결할 수 있는지 고려해야합니다 .
다형성을 사용하면 다음과 같습니다.
foreach (var animal in zoo) {
switch (typeof(animal)) {
case "dog":
echo animal.bark();
break;
case "cat":
echo animal.meow();
break;
}
}
이된다 :
foreach (var animal in zoo) {
echo animal.speak();
}
typeof
답변에서는 다른 상황에서 switch 문을 해결하는 방법이나 이유를 제안하지 않습니다.
스위치 선언문 냄새 참조 :
일반적으로 유사한 스위치 문이 프로그램 전체에 흩어져 있습니다. 하나의 스위치에서 절을 추가하거나 제거하면 다른 스위치도 찾아서 복구해야합니다.
리팩토링 과 패턴 리팩토링 모두이 문제를 해결하는 방법이 있습니다.
(의사) 코드가 다음과 같은 경우
class RequestHandler {
public void handleRequest(int action) {
switch(action) {
case LOGIN:
doLogin();
break;
case LOGOUT:
doLogout();
break;
case QUERY:
doQuery();
break;
}
}
}
이 코드는 공개 폐쇄 원칙을 위반하며 발생 하는 모든 새로운 유형의 조치 코드에 취약합니다. 이를 해결하기 위해 'Command'객체를 도입 할 수 있습니다.
interface Command {
public void execute();
}
class LoginCommand implements Command {
public void execute() {
// do what doLogin() used to do
}
}
class RequestHandler {
private Map<Integer, Command> commandMap; // injected in, or obtained from a factory
public void handleRequest(int action) {
Command command = commandMap.get(action);
command.execute();
}
}
(의사) 코드가 다음과 같은 경우
class House {
private int state;
public void enter() {
switch (state) {
case INSIDE:
throw new Exception("Cannot enter. Already inside");
case OUTSIDE:
state = INSIDE;
...
break;
}
}
public void exit() {
switch (state) {
case INSIDE:
state = OUTSIDE;
...
break;
case OUTSIDE:
throw new Exception("Cannot leave. Already outside");
}
}
그런 다음 'State'객체를 소개 할 수 있습니다.
// Throw exceptions unless the behavior is overriden by subclasses
abstract class HouseState {
public HouseState enter() {
throw new Exception("Cannot enter");
}
public HouseState leave() {
throw new Exception("Cannot leave");
}
}
class Inside extends HouseState {
public HouseState leave() {
return new Outside();
}
}
class Outside extends HouseState {
public HouseState enter() {
return new Inside();
}
}
class House {
private HouseState state;
public void enter() {
this.state = this.state.enter();
}
public void leave() {
this.state = this.state.leave();
}
}
도움이 되었기를 바랍니다.
Map<Integer, Command>
스위치가 필요하지 않습니까?
스위치는 스위치 문으로 구현되는지 여부에 관계없이 체인, 룩업 테이블, oop 다형성, 패턴 일치 또는 다른 것입니다.
" switch statement "또는 " switch pattern "을 사용하지 않겠습니까? 첫 번째 것은 다른 패턴 / 알고리즘을 사용할 수있는 경우에만 제거 할 수 있으며, 대부분 불가능하거나 더 나은 방법은 아닙니다.
코드 에서 switch 문 을 제거하려면 첫 번째 질문 은 switch 문을 제거하고 다른 기술을 사용 하는 것이 합리적 입니다. 불행히도이 질문에 대한 답변은 도메인마다 다릅니다.
컴파일러는 명령문을 전환하기 위해 다양한 최적화를 수행 할 수 있습니다. 예를 들어, 메시지 처리를 효율적으로 수행하려면 switch 문을 사용하는 것이 좋습니다. 그러나 다른 한편으로는 switch 문을 기반으로 비즈니스 규칙을 실행하는 것이 가장 좋은 방법은 아니며 응용 프로그램을 재구성해야합니다.
switch 문에 대한 대안은 다음과 같습니다.
스위치 자체는 그렇게 나쁘지는 않지만 메서드의 객체에 "스위치"또는 "if / else"가 많은 경우 디자인이 약간 "절차 적"이고 객체가 가치가 있다는 신호일 수 있습니다 양동이. 논리를 객체로 옮기고 객체에서 메소드를 호출 한 다음 대신 응답 방법을 결정하도록합니다.
가장 좋은 방법은 좋은지도를 사용하는 것입니다. 사전을 사용하면 거의 모든 입력을 다른 값 / 객체 / 함수에 매핑 할 수 있습니다.
코드는 다음과 같이 보일 것입니다.
void InitMap(){
Map[key1] = Object/Action;
Map[key2] = Object/Action;
}
Object/Action DoStuff(Object key){
return Map[key];
}
switch
명령문에 새로운 상태 또는 새로운 동작을 추가하면 명령문을 대체하는 것이 좋습니다.
int 상태; 문자열 getString () { 스위치 (상태) { case 0 : // 상태 0에 대한 동작 "제로"를 반환; case 1 : // 상태 1의 동작 "하나"를 반환; } 새로운 IllegalStateException () 던지기; } 이중 getDouble () { 스위치 (this.state) { case 0 : // 상태 0에 대한 동작 리턴 0d; case 1 : // 상태 1의 동작 1d를 반환; } 새로운 IllegalStateException () 던지기; }
새로운 동작을 추가하려면을 복사해야하며 switch
새로운 상태를 추가 case
하면 모든 switch
명령문에 다른 상태를 추가 해야 합니다.
Java에서는 런타임시 값을 알고있는 매우 제한된 수의 기본 유형 만 전환 할 수 있습니다. 이것은 그 자체로 문제를 제시합니다 : 상태는 마법의 숫자 나 문자로 표현됩니다.
if - else
새로운 동작과 새로운 상태를 추가 할 때 실제로 동일한 문제가 있지만 패턴 일치 및 여러 블록을 사용할 수 있습니다.
다른 사람들이 "다형성 (polymorphism)"으로 제안한 솔루션은 State 패턴 의 인스턴스입니다 .
각 상태를 자체 클래스로 바꿉니다. 각 행동에는 클래스에 대한 자체 메소드가 있습니다.
국가 상태; 문자열 getString () { return state.getString (); } 이중 getDouble () { return state.getDouble (); }
새로운 상태를 추가 할 때마다 새로운 IState
인터페이스 구현을 추가해야합니다 . switch
세계 에서는 case
각에를 추가 할 것 switch
입니다.
새로운 행동을 추가 할 때 IState
마다 인터페이스와 각 구현에 새로운 메소드를 추가해야합니다 . 이것은 컴파일러가 이전과 동일한 부담이지만, 이제 컴파일러는 기존의 각 상태에 대해 새로운 동작의 구현이 있는지 확인합니다.
다른 사람들은 이미 이것이 너무 무거울 수 있다고 말 했으므로 물론 당신이 서로 이동하는 지점에 도달 할 수 있습니다. 개인적으로 두 번째로 스위치를 작성할 때는 리팩터링하는 시점입니다.
'스위치'는 언어 구성 일 뿐이며 모든 언어 구성은 작업을 수행하기위한 도구로 생각할 수 있습니다. 실제 도구와 마찬가지로 일부 도구는 다른 도구보다 한 작업에 더 적합합니다 (그림 고리를 걸기 위해 썰매 망치를 사용하지 않음). 중요한 부분은 '작업 완료'가 어떻게 정의되는지입니다. 유지 관리가 가능해야하고, 빠르거나, 확장이 필요하고, 확장 가능해야 하는가 등이 있습니다.
프로그래밍 프로세스의 각 지점에는 일반적으로 스위치, if-else-if 시퀀스, 가상 함수, 점프 테이블, 함수 포인터가있는 맵 등 사용할 수있는 다양한 구문과 패턴이 있습니다. 경험이 있으면 프로그래머는 주어진 상황에 적합한 도구를 본능적으로 알게됩니다.
코드를 유지 관리하거나 검토하는 사람은 최소한 원본 작성자만큼 숙련되어 모든 구성을 안전하게 사용할 수 있다고 가정해야합니다.
C ++ 용
즉, AbstractFactory를 참조하는 경우 registerCreatorFunc (..) 메소드가 일반적으로 필요한 각각의 모든 "새"문에 대해 대소 문자를 추가하는 것보다 낫다고 생각합니다. 그런 다음 모든 클래스가 creatorFunction (..) 을 생성하고 등록 하게하여 매크로로 쉽게 구현할 수 있습니다. 나는 이것이 많은 프레임 워크가하는 일반적인 접근 방식이라고 생각합니다. 나는 먼저 ET ++에서 그것을 보았고 DECL과 IMPL 매크로가 필요한 많은 프레임 워크가 그것을 사용한다고 생각합니다.
C와 같은 절차 적 언어에서 스위치는 다른 대안보다 낫습니다.
객체 지향 언어에는 객체 구조, 특히 다형성을 더 잘 활용하는 다른 대안이 거의 항상 있습니다.
스위치 문의 문제는 응용 프로그램의 여러 위치에서 매우 유사한 여러 스위치 블록이 발생할 때 발생하며 새로운 값에 대한 지원이 추가되어야합니다. 개발자가 애플리케이션 주위에 흩어져있는 스위치 블록 중 하나에 새로운 값에 대한 지원을 추가하는 것을 잊어 버리는 것이 일반적입니다.
다형성을 사용하면 새 클래스가 새 값을 대체하고 새 클래스를 추가 할 때 새 동작이 추가됩니다. 그런 다음 이러한 스위치 지점에서의 동작은 수퍼 클래스에서 상속되거나 새로운 동작을 제공하도록 재정의되거나 수퍼 메소드가 추상 일 때 컴파일러 오류를 피하기 위해 구현됩니다.
명백한 다형성이 진행되지 않는 경우 전략 패턴을 구현할 가치가 있습니다 .
그러나 대안이 큰 IF ... THEN ... ELSE 블록이면 잊어 버려라.
함수 포인터는 거대한 chunky switch 문을 대체하는 한 가지 방법입니다. 특히 함수 이름으로 함수를 캡처하고 만들 수있는 언어에 적합합니다.
물론 코드에서 스위치 문을 강제로 실행해서는 안되며, 항상 모든 잘못된 일을 할 가능성이 있습니다. 이로 인해 어리석은 중복 코드가 생성됩니다. (이것은 때로는 피할 수 없지만 좋은 언어를 사용하면 깨끗하게 유지하면서 중복성을 제거 할 수 있습니다.)
이것은 훌륭한 분할 및 정복 예제입니다.
어떤 종류의 통역사가 있다고 가정하십시오.
switch(*IP) {
case OPCODE_ADD:
...
break;
case OPCODE_NOT_ZERO:
...
break;
case OPCODE_JUMP:
...
break;
default:
fixme(*IP);
}
대신 다음을 사용할 수 있습니다.
opcode_table[*IP](*IP, vm);
... // in somewhere else:
void opcode_add(byte_opcode op, Vm* vm) { ... };
void opcode_not_zero(byte_opcode op, Vm* vm) { ... };
void opcode_jump(byte_opcode op, Vm* vm) { ... };
void opcode_default(byte_opcode op, Vm* vm) { /* fixme */ };
OpcodeFuncPtr opcode_table[256] = {
...
opcode_add,
opcode_not_zero,
opcode_jump,
opcode_default,
opcode_default,
... // etc.
};
C에서 opcode_table의 중복성을 제거하는 방법을 모르겠습니다. 아마도 그것에 대해 질문해야 할 것입니다. :)
스위치는 Open Close Principal을 위반하므로 좋은 방법이 아닙니다. 이것이 내가하는 방법입니다.
public class Animal
{
public abstract void Speak();
}
public class Dog : Animal
{
public virtual void Speak()
{
Console.WriteLine("Hao Hao");
}
}
public class Cat : Animal
{
public virtual void Speak()
{
Console.WriteLine("Meauuuu");
}
}
그리고 그것을 사용하는 방법은 다음과 같습니다 (코드 사용) :
foreach (var animal in zoo)
{
echo animal.speak();
}
기본적으로 우리가하고있는 일은 부모가 자녀와 함께 무엇을해야할지 결정하는 대신 자녀 클래스에 책임을 위임하는 것입니다.
"Liskov 대체 원칙"을 읽어 보길 원할 수도 있습니다.
연관 배열을 사용하는 JavaScript에서
:
function getItemPricing(customer, item) {
switch (customer.type) {
// VIPs are awesome. Give them 50% off.
case 'VIP':
return item.price * item.quantity * 0.50;
// Preferred customers are no VIPs, but they still get 25% off.
case 'Preferred':
return item.price * item.quantity * 0.75;
// No discount for other customers.
case 'Regular':
case
default:
return item.price * item.quantity;
}
}
이된다 :
function getItemPricing(customer, item) {
var pricing = {
'VIP': function(item) {
return item.price * item.quantity * 0.50;
},
'Preferred': function(item) {
if (item.price <= 100.0)
return item.price * item.quantity * 0.75;
// Else
return item.price * item.quantity;
},
'Regular': function(item) {
return item.price * item.quantity;
}
};
if (pricing[customer.type])
return pricing[customer.type](item);
else
return pricing.Regular(item);
}
if / else에 대한 또 다른 투표. 나는 그것을 사용하지 않는 사람들이 있기 때문에 대 소문이나 스위치 진술에 열광하지 않습니다. case 또는 switch를 사용하면 코드를 쉽게 읽을 수 없습니다. 어려울 수도 있지만 명령을 사용할 필요가없는 사용자에게는 읽기 어려울 수도 있습니다.
객체 팩토리도 마찬가지입니다.
If / else 블록은 누구나 얻을 수있는 간단한 구성입니다. 문제가 발생하지 않도록하기 위해 할 수있는 일이 몇 가지 있습니다.
첫째-if 문을 두 번 이상 시도하지 마십시오. 들여 쓰기를 발견하면 잘못하고 있습니다.
if a = 1 then
do something else
if a = 2 then
do something else
else
if a = 3 then
do the last thing
endif
endif
endif
정말 나쁘다-대신 이것을하십시오.
if a = 1 then
do something
endif
if a = 2 then
do something else
endif
if a = 3 then
do something more
endif
최적화가 저주되었습니다. 코드 속도와 크게 다르지 않습니다.
둘째, 특정 코드 블록을 통해 흩어져있는 break 문이 충분하기 만하면 If 블록에서 벗어나는 것을 피하지 않습니다.
procedure processA(a:int)
if a = 1 then
do something
procedure_return
endif
if a = 2 then
do something else
procedure_return
endif
if a = 3 then
do something more
procedure_return
endif
end_procedure
편집 : On Switch와 왜 어려운지 생각합니다.
다음은 switch 문의 예입니다 ...
private void doLog(LogLevel logLevel, String msg) {
String prefix;
switch (logLevel) {
case INFO:
prefix = "INFO";
break;
case WARN:
prefix = "WARN";
break;
case ERROR:
prefix = "ERROR";
break;
default:
throw new RuntimeException("Oops, forgot to add stuff on new enum constant");
}
System.out.println(String.format("%s: %s", prefix, msg));
}
나에게 여기서 문제는 C와 같은 언어로 적용되는 일반적인 제어 구조가 완전히 손상되었다는 것입니다. 제어 구조 내에 둘 이상의 코드 줄을 배치하려면 중괄호 또는 begin / end 문을 사용하는 일반적인 규칙이 있습니다.
예 :
for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}
나를 위해 (그리고 내가 틀렸다면 나를 교정 할 수있다) Case 문은이 규칙을 창 밖으로 던져 버린다. 조건부로 실행 된 코드 블록은 시작 / 종료 구조 내에 배치되지 않습니다. 이 때문에 Case는 사용하지 않을 정도로 개념적으로 다르다고 생각합니다.
질문에 대한 답변을 바랍니다.