1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
| // 문제 상황: 텍스트 에디터에서 백만 개의 문자를 렌더링
// 각 문자마다 폰트, 크기, 색상 정보를 개별적으로 저장한다면?
// 비효율적인 방법
class Character_BAD {
private char c;
private String fontFamily; // "Arial" (반복!)
private int fontSize; // 12 (반복!)
private Color color; // Color.BLACK (반복!)
private int x, y; // 위치는 각자 다름
// 1,000,000개 문자 × 100 bytes = 100MB 메모리 사용
}
// Flyweight 방법으로 해결
// 공통된 부분(폰트 정보)은 공유하고, 개별적인 부분(위치)만 따로 저장
// Flyweight 인터페이스
interface CharacterFlyweight {
void render(Canvas canvas, int x, int y, Color color);
int getWidth();
int getHeight();
}
// 구체적 Flyweight - 내재적 상태만 보유
class ConcreteCharacter implements CharacterFlyweight {
private final char character; // 내재적 상태
private final String fontFamily; // 내재적 상태
private final int fontSize; // 내재적 상태
private final byte[] glyphData; // 내재적 상태 (폰트 렌더링 데이터)
// 한 번 생성되면 변경되지 않음 (불변 객체)
public ConcreteCharacter(char character, String fontFamily, int fontSize) {
this.character = character;
this.fontFamily = fontFamily;
this.fontSize = fontSize;
this.glyphData = loadGlyphData(character, fontFamily, fontSize);
}
@Override
public void render(Canvas canvas, int x, int y, Color color) {
// x, y, color는 외재적 상태로 매개변수로 받음
canvas.setColor(color);
canvas.drawGlyph(glyphData, x, y);
}
@Override
public int getWidth() {
return calculateWidth(glyphData);
}
@Override
public int getHeight() {
return fontSize;
}
private byte[] loadGlyphData(char c, String font, int size) {
// 실제로는 폰트 파일에서 글리프 데이터를 로딩
System.out.println("Loading glyph data for '" + c + "' in " + font + " " + size + "pt");
return new byte[1024]; // 가상의 글리프 데이터
}
private int calculateWidth(byte[] glyphData) {
// 글리프 데이터에서 너비 계산
return fontSize / 2; // 간단한 예시
}
}
// Flyweight Factory - 객체 공유 관리
class CharacterFlyweightFactory {
private static final Map<String, CharacterFlyweight> flyweights = new ConcurrentHashMap<>();
public static CharacterFlyweight getCharacter(char c, String fontFamily, int fontSize) {
String key = c + "_" + fontFamily + "_" + fontSize;
return flyweights.computeIfAbsent(key, k -> {
System.out.println("Creating new flyweight for: " + key);
return new ConcreteCharacter(c, fontFamily, fontSize);
});
}
public static int getFlyweightCount() {
return flyweights.size();
}
public static void printStatistics() {
System.out.println("Total flyweights created: " + flyweights.size());
System.out.println("Memory saved: " + calculateMemorySaved() + " MB");
}
private static long calculateMemorySaved() {
// 단순 계산 예시
return flyweights.size() * 100; // 각 flyweight가 100KB 절약한다고 가정
}
}
// Context - 외재적 상태 보유
class CharacterContext {
private final int x, y; // 외재적 상태 (위치)
private final Color color; // 외재적 상태 (색상)
private final CharacterFlyweight flyweight; // Flyweight 참조
public CharacterContext(int x, int y, Color color, char c, String fontFamily, int fontSize) {
this.x = x;
this.y = y;
this.color = color;
this.flyweight = CharacterFlyweightFactory.getCharacter(c, fontFamily, fontSize);
}
public void render(Canvas canvas) {
flyweight.render(canvas, x, y, color);
}
public int getX() { return x; }
public int getY() { return y; }
public Color getColor() { return color; }
public CharacterFlyweight getFlyweight() { return flyweight; }
}
// 텍스트 문서 - Flyweight 활용
class TextDocument {
private List<CharacterContext> characters = new ArrayList<>();
private String defaultFontFamily = "Arial";
private int defaultFontSize = 12;
public void addText(String text, int startX, int startY, Color color) {
int currentX = startX;
int currentY = startY;
for (char c : text.toCharArray()) {
if (c == '\n') {
currentX = startX;
currentY += defaultFontSize + 2; // 줄 간격
continue;
}
CharacterContext context = new CharacterContext(
currentX, currentY, color, c, defaultFontFamily, defaultFontSize
);
characters.add(context);
// 다음 문자 위치 계산
currentX += context.getFlyweight().getWidth();
}
}
public void render(Canvas canvas) {
System.out.println("Rendering document with " + characters.size() + " characters");
for (CharacterContext character : characters) {
character.render(canvas);
}
}
public void printMemoryUsage() {
System.out.println("Document statistics:");
System.out.println("- Total characters: " + characters.size());
System.out.println("- Unique flyweights: " + CharacterFlyweightFactory.getFlyweightCount());
// 메모리 절약 계산
long withoutFlyweight = characters.size() * 100L; // 각 문자당 100 bytes
long withFlyweight = CharacterFlyweightFactory.getFlyweightCount() * 100L + characters.size() * 20L; // flyweight + context
long saved = withoutFlyweight - withFlyweight;
System.out.println("- Memory without Flyweight: " + withoutFlyweight + " bytes");
System.out.println("- Memory with Flyweight: " + withFlyweight + " bytes");
System.out.println("- Memory saved: " + saved + " bytes (" +
(saved * 100 / withoutFlyweight) + "% reduction)");
}
}
// 사용 예시
public class FlyweightPatternExample {
public static void main(String[] args) {
Canvas canvas = new MockCanvas();
TextDocument document = new TextDocument();
// 대량의 텍스트 추가 (현실적인 시나리오)
document.addText("Hello World! This is a sample text.", 10, 10, Color.BLACK);
document.addText("Hello World! This is another line.", 10, 30, Color.BLUE);
document.addText("Same characters appear multiple times.", 10, 50, Color.BLACK);
// 더 많은 텍스트 추가 (Flyweight 효과 확인)
for (int i = 0; i < 100; i++) {
document.addText("Line " + i + ": Hello World!", 10, 70 + i * 20,
i % 2 == 0 ? Color.BLACK : Color.BLUE);
}
// 렌더링
document.render(canvas);
// 메모리 사용량 분석
document.printMemoryUsage();
CharacterFlyweightFactory.printStatistics();
/*
* 출력 예시:
* Creating new flyweight for: H_Arial_12
* Creating new flyweight for: e_Arial_12
* Creating new flyweight for: l_Arial_12
* Creating new flyweight for: o_Arial_12
* Creating new flyweight for: _Arial_12 (공백)
* ...
*
* Document statistics:
* - Total characters: 3847
* - Unique flyweights: 26 (a-z, A-Z, 0-9, 공백, 특수문자 등)
* - Memory without Flyweight: 384,700 bytes
* - Memory with Flyweight: 79,540 bytes
* - Memory saved: 305,160 bytes (79% reduction)
*/
}
}
// Mock Canvas for demonstration
class MockCanvas implements Canvas {
private Color currentColor = Color.BLACK;
@Override
public void setColor(Color color) {
this.currentColor = color;
}
@Override
public void drawGlyph(byte[] glyphData, int x, int y) {
// 실제로는 화면에 그리기
// System.out.println("Drawing glyph at (" + x + "," + y + ") with color " + currentColor);
}
}
interface Canvas {
void setColor(Color color);
void drawGlyph(byte[] glyphData, int x, int y);
}
enum Color {
BLACK, BLUE, RED, GREEN
}
|