U E D R , A S I H C RSS

Smalltalk Best Practice Patterns/Dispatched Interpretation

How can two objects cooperate when one wishes to conceal its representation ?
ν•˜λ‚˜μ˜ 객체가 κ·Έκ²ƒμ˜ ν‘œν˜„(Representation)을 숨기기λ₯Ό λ°”λž„ λ•Œ μ–΄λ–»κ²Œ 두 객체듀은 ν˜‘λ ₯(Cooperate)ν•  수 μžˆλŠ”κ°€ ?

Encoding is inevitable in programming. At some point you say, "Here is some information. How am I going to represent it?" This decision to encode information happens a hundred times a day.

인코딩은 ν”„λ‘œκ·Έλž˜λ°μ— μžˆμ–΄μ„œ ν•„μˆ˜μ μ΄λ‹€. λͺ‡λͺ‡ κ΄€μ μ—μ„œ 당신은 λ§ν•œλ‹€, "μ•½κ°„μ˜ 정보가 μžˆλ‹€. μ–΄λ–»κ²Œ 그것을 ν‘œν˜„ν•  수 μžˆλŠ”κ°€?" 정보λ₯Ό μΈμ½”λ”©ν•˜λŠ” 결정은 ν•˜λ£¨μ—λ„ μ•„μ£Ό 많이(a hundred times a day) λ°œμƒν•œλ‹€.

Back in the days when data was separated from computation, and seldom the twain should meet, encoding decisions were critical. Any encoding decision you made was propagated to many different parts of the computation. If you got the encoding wrong, the cost of change was enormous. The longer it took to find the mistake, the more ridiculous the bill.

과거둜 λŒμ•„κ°€μ„œ 데이타가 μ—°μ‚°μœΌλ‘œλΆ€ν„° λΆˆλ¦¬λ˜μ—ˆμ„ λ•Œ, 그리고 μ’…μ’… κ·Έ λ‘˜μ΄ λ§Œλ‚˜μ•Ό ν–ˆμ„ λ•Œ, 인코딩 결정은 μ€‘λŒ€ν•œ κ²ƒμ΄μ—ˆλ‹€. λ„ˆμ˜ μ–΄λ– ν•œ 인코딩 결정은 μ—°μ‚°μ˜ λ§Žμ€ λ‹€λ₯Έ 뢀뢄듀을 점차적으둜 μ¦κ°€μ‹œμΌœλ‚˜μ•„κ°”λ‹€. λ§Œμ•½ 잘λͺ»λœ 인코딩을 ν•œλ‹€λ©΄, λ³€ν™”μ˜ λΉ„μš©μ€ λ§‰λŒ€ν•˜λ‹€. The longer it took to find the mistake, the more ridiculous the bill.

Objects change all this. How you distribute responsibility among objects is the critical decision, encoding is a distant second. For the most part, in well factored programs, only a single object is interested in a piece of information. That object directly references the information and privately performs all the needed encoding and decoding.

객체듀 μ‚¬μ΄μ—μ„œ μ–΄λ–»κ²Œ μ±…μž„μ„ λΆ„λ°°ν•˜λŠ”κ°€λŠ” μ€‘μš”ν•œ 결정이닀, 인코딩은 κ·Έ λ‹€μŒμ΄λ‹€. 잘 μš”μ†Œν™”λœ ν”„λ‘œκ·Έλž¨λ“€μ˜ λŒ€λΆ€λΆ„μ—μ„œ, 였직 ν•˜λ‚˜μ˜ 객체만이 ν•œ 쑰각의 정보에 관심을 κ°€μ§„λ‹€. κ°μ²΄λŠ” 정보λ₯Ό μ§μ ‘μ μœΌλ‘œ μ°Έμ‘°ν•˜κ³  μ‚¬μ μœΌλ‘œ ν•„μš”ν•œ λͺ¨λ“  인코딩과 디코딩을 μˆ˜ν–‰ν•œλ‹€.

Sometimes, however, information in one object must influence the behavior of another. When the uses of the information are simple, or the possible choices based on the information limited, it is sufficient to send a message to the encoded object. Thus, the fact that boolean values are represented as instances of one of two classes, True and False, is hidden behind the message #ifTrue:ifFalse:.

λ•Œλ•Œλ‘œ, κ·ΈλŸ¬λ‚˜, ν•œ 객체의 μ •λ³΄λŠ” λ‹€λ₯Έ 객체의 ν–‰μœ„μ— 영ν–₯을 λ―ΈμΉ  것이닀. μ •λ³΄μ˜ μ‚¬μš©μ΄ λ‹¨μˆœν•  경우, λ˜λŠ” μ œν•œλœ 정보에 κΈ°λ°˜ν•΄μ„œ 선택이 κ°€λŠ₯ν•  κ²½μš°μ—λŠ” μΈμ½”λ”©λœ κ°μ²΄μ—κ²Œ λ©”μ‹œμ§€λ₯Ό λ³΄λ‚΄λŠ” 것이 μΆ©λΆ„νžˆ κ°€λŠ₯ν•˜λ‹€. 이와 같이, λΆ€μšΈλ¦° 값듀은 두 클래슀의 ν•˜λ‚˜μ˜ μΈμŠ€ν„΄μŠ€λ‘œ ν‘œν˜„λ˜μ–΄μ§„λ‹€λŠ” 사싀은, True 그리고 False, λ©”μ‹œμ§€ #ifTrue: ifFalse:.λ’€λ‘œ μˆ¨κ²¨μ§„λ‹€

~cpp 
True>>ifTrue:trueBlock ifFalse:falseBlock
     ^trueBlock value
False>>ifTrue:trueBlock ifFalse:falseBlock
      ^falseBlock value

We could encode boolean values some other way, and as long as we provided the same protocol, no client would be the wiser.

Sets interact with their elements like this. Regardless of how an object is represented, as long it can respond to #=and #hash, it can be put in a Set.

Sometimes, encoding decisions can be hidden behind intermediate objects. And ASCII String encoded as eight-bit bytes hides that fact by conversing with the outside world in terms of Characters:

~cpp 
String>>at: anInteger
      ^Character asciiValue: (self basicAt: anInteger)


When there are many different types of information to be encoded, and the behavior of clients changes based on the information, these simple strategies won't work. The problem is that you don't want each of a hundred clients to explicitly record in a case statement what all the types of information are.

For example, consider a graphical Shape represented by a sequence of line, curve, stroke, and fill commands. Regardless of how the Shape is represented internally, it can provide a message #commandAt: anInteger that returns a Symbol representing the command and #argumentsAt: anInteger that returns an array of arguments. We could use these messages to write a PostScriptShapePrinter that would convert a Shape to PostScript:

~cpp 
PostScriptShapePrinter>>display: aShape
    1 to: aShape size do:
           [:each || command arguments |
           command := aShape commantAt: each.
           arguments := aShape argumentsAt: each.
           command = #line if True:
               [self
                   printPoint: (arguments at: 1);
                   space;
                   printPoint: (arguments at: 2);
                   space;
                   nextPutAll:'line'].
                command = #curve...
                ...]

Every client that wanted to make decisions based on what commands where in a Shape would have to have the same case statement, violating the "once and only once" rule. We need a solution where the case statement is hidden inside of the encoded objects.

* Have the client send a message to the encoded object. PAss a parameter to which the encoded object will send decoded messages.

The simplest example of this is Collection>>do:. By passing a one argument Block(or any other object that responds to #value:), you are assured that the code will work, no matter whether the Collection is encoded as a linear list, an array, a hash table, or a balanced tree.

This is a simplified case of Dispatched Interpretation because there is only a single message coming back. For the most part, there will be several messages. For example, we can use this pattern with the Shape example. Rather than have a case statement for every command, we have a method in PostScriptShapePrinter for every command, For example:

~cpp 
PostScriptShapePrinter>>lineFrom: fromPoint to: toPoint 
     self
           printPoint: fromPoint;
           space;
           printPoint: toPoint;
           space;
           nextPutAll: 'line'

Rather than Shapes providing #commandAt: and #argumentsAt:, they provide #sendCommantAt: anInteger to: anObject, where #lineFrom:to: is one of the messages that could be sent back. Then the original display code could read:

~cpp 
PostScriptShapePrinter>>display:aShape
     1 to: aShape size do:
            [:each |
            aShape
                sendCommandAt: each
                to: self]

This could be further simplified by giving Shapes the responsibility to iterate over themselves:

~cpp 
Shape>>sendCommandsTo: anObject
     1 to: self size do:
           [:each |
           self
                 sendCommandAt: each
                 to: anObject]

PostScriptShapePrinter>>display: aShape
     aShape sendCommandsTo: self

The name "dispatched interpretation" comes from the distribution of responsibility. The encoded object "dispatches" a message to the client. The client "interprets" the message. Thus, the Shape dispatches message like #lineFrom:to: and #curveFrom:mid:to:. It's up to the clients to interpret the messages, with the PostScriptShapePrinter creating PostScript and the ShapeDisplayer displaying on the screen.

You will have to design a Mediating Protocol of messgaes to be sent back. Computations where both objects have decoding to do need Double Dispatch.
Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2021-02-07 05:28:04
Processing time 0.0178 sec