架构师训练营作业 3

发布于: 2020 年 06 月 24 日

1. 在草稿纸上手写一个单例模式的实现代码

白板面试的重要性:https://www.pingwest.com/a/51826。考虑到Homebrew的原作者Max Howell去Google面试都被手写算法通不过被拒之门外,对一线码农来说还是需要多多练习的(手动狗头)。

2. 请用组合设计模式编写程序,打印输出图 1 的窗口,窗口组件的树结构如图 2 所示,打印输出示例参考图 3

Swing实现分析

在撸码实现之前,不妨先来看一下Java Swing的设计思路,下面是最简单的一个Java界面开发代码样例:

import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.*;
public class MyFrame extends JFrame
{
public MyFrame()
{
JFrame frame = new JFrame("MyFrame");
frame.setBounds(300, 100, 400, 200);
JPanel panel = new JPanel();
JLabel label = new JLabel("This is a label");
panel.setBackground(Color.orange);
panel.add(label);
frame.add(panel);
frame.setVisible(true);
}
public static void main(String[] agrs)
{
new MyFrame();
}
}

在Mac操作系统上展示效果如下:

与我们作业实现相关的话,涉及到两个关键点:

  1. 控件之间是如何建立关联关系的?

  2. 窗体及内部控件是如何绘制出来的?

带着问题我们来粗读一下Swing的内部源码:

先来看下第一个问题,从frame.add(panel)这行代码入手,首先来看下JFrame和JPanel的类层级结构:

JFrame:

JPanel:

可以看到整个类层级和老师课件中体现的组合模式的应用是基本一致的,所有的界面控件都继承自基类Component,控件分为可嵌套子控件类型(如JFrame)和不可嵌套子控件类型(如JMenuItem),前者又抽象出一个基类定义Container,同时Container也是一个Component。通过良好的定义抽象和继承关系设计,使得Swing框架的可扩展性和复用性都很强,代码编写起来也很符合直觉。

add方法在Container类当中,被所有可嵌套子控件的控件类共用,其代码如下:

public Component add(Component comp) {
addImpl(comp, null, -1);
return comp;
}

再来看add方法的具体实现addImpl:

protected void addImpl(Component comp, Object constraints, int index) {
synchronized (getTreeLock()) {
// 无关代码
...
/* Reparent the component and tidy up the tree's state. */
if (comp.parent != null) {
comp.parent.remove(comp);
if (index > component.size()) {
throw new IllegalArgumentException("illegal component position");
}
}
//index == -1 means add to the end.
if (index == -1) {
component.add(comp);
} else {
component.add(index, comp);
}
comp.parent = this;
comp.setGraphicsConfiguration(thisGC);
// 无关代码
...
}
}

可以看到方法中关键的一行为:component.add(comp); 这里的component实际上是Container类当中的一个成员变量(不得不吐槽一下这个变量命名,列表成员变量命名为啥不带s或List后缀...):

public class Container extends Component {
/**
* The components in this container.
* @see #add
* @see #getComponents
*/
private java.util.List<Component> component = new ArrayList<>();
}

这里第一个问题基本能得到解答,其实比较明显的能看出组合模式的思路了。每个Container包含零个或多个Component或Container(Container也是一种Component),不妨猜想在Container实际绘制的过程当中,就会遍历component列表中的子控件进行递归式的绘制。

通过查看官方文档,可以得知在Swing中,控件的绘制工作是由paint函数实现的,在控件基类Component当中,paint被实现为一个空函数:

/**
* Paints this component.
* <p>
* This method is called when the contents of the component should
* be painted; such as when the component is first being shown or
* is damaged and in need of repair. The clip rectangle in the
* <code>Graphics</code> parameter is set to the area
* which needs to be painted.
* Subclasses of <code>Component</code> that override this
* method need not call <code>super.paint(g)</code>.
* <p>
* For performance reasons, <code>Component</code>s with zero width
* or height aren't considered to need painting when they are first shown,
* and also aren't considered to need repair.
* <p>
* <b>Note</b>: For more information on the paint mechanisms utilitized
* by AWT and Swing, including information on how to write the most
* efficient painting code, see
* <a href="http://www.oracle.com/technetwork/java/painting-140037.html">Painting in AWT and Swing</a>.
*
* @param g the graphics context to use for painting
* @see #update
* @since JDK1.0
*/
public void paint(Graphics g) {
}

从方法注释中其实也可以看出该函数是专门被设计用于子类实现的。短短一个函数体现了多个设计模式,其一是策略模式,不同的控件有不同的绘制策略。其二是模板方法模式,开发者一般并不会主动调用paint函数,但其会在整个绘制模板流程中被调用,执行特定控件的绘制逻辑,如果开发者需要自定义一个新的组件,只需继承Component并覆写paint方法即可。

Container作为Component的子类,对paint方法进行了重写如下:

public void paint(Graphics g) {
if (isShowing()) {
// 无关代码
...
GraphicsCallback.PaintCallback.getInstance().
runComponents(getComponentsSync(), g, GraphicsCallback.LIGHTWEIGHTS);
}
}

getComponentsSync方法即加锁方式获取上文中提到的component列表,继续尝试分析runComponents方法:

public final void runComponents(Component[] var1, Graphics var2, int var3) {
int var4 = var1.length;
Shape var5 = var2.getClip();
if (log.isLoggable(Level.FINER) && var5 != null) {
Rectangle var6 = var5.getBounds();
log.finer("x = " + var6.x + ", y = " + var6.y + ", width = " + var6.width + ", height = " + var6.height);
}
int var7;
if ((var3 & 4) != 0) {
for(var7 = var4 - 1; var7 >= 0; --var7) {
this.runOneComponent(var1[var7], (Rectangle)null, var2, var5, 2); // 重要!!
}
for(var7 = var4 - 1; var7 >= 0; --var7) {
this.runOneComponent(var1[var7], (Rectangle)null, var2, var5, 1);
}
} else {
for(var7 = var4 - 1; var7 >= 0; --var7) {
this.runOneComponent(var1[var7], (Rectangle)null, var2, var5, var3);
}
}
}
public final void runOneComponent(Component var1, Rectangle var2, Graphics var3, Shape var4, int var5) {
if (var1 != null && var1.getPeer() != null && var1.isVisible()) {
boolean var6 = var1.isLightweight();
if ((!var6 || (var5 & 2) != 0) && (var6 || (var5 & 1) != 0)) {
if (var2 == null) {
var2 = var1.getBounds();
}
if (var4 == null || var4.intersects(var2)) {
Graphics var7 = var3.create();
try {
this.constrainGraphics(var7, var2);
var7.setFont(var1.getFont());
var7.setColor(var1.getForeground());
if (var7 instanceof Graphics2D) {
((Graphics2D)var7).setBackground(var1.getBackground());
} else if (var7 instanceof Graphics2Delegate) {
((Graphics2Delegate)var7).setBackground(var1.getBackground());
}
this.run(var1, var7); // 重要!!
} finally {
var7.dispose();
}
}
}
}
}
static final class PaintCallback extends GraphicsCallback {
private static PaintCallback instance = new PaintCallback();
private PaintCallback() {}
public void run(Component comp, Graphics cg) {
comp.paint(cg); // 重要!!
}
static PaintCallback getInstance() {
return instance;
}
}

这块儿代码说实话已经有点儿绕不懂了... 不过凭直觉屡下来基本可以确定是遍历component列表调用paint函数进行递归的绘制,与我们最开始的猜想应该是基本吻合的,所以第二个问题基本也解了。

作业实现

通过上述的分析,作业实现可仿照Swing的设计思路来做,具体代码如下。

// MyComponent.java
public abstract class MyComponent {
/**
* 绘制控件
*/
abstract void paint();
}
// MyContainer.java
public abstract class MyContainer extends MyComponent {
private final List<MyComponent> components = new ArrayList<>();
@Override
public void paint() {
paintComponent();
paintChildren();
}
/**
* 绘制自身控件
*/
abstract void paintComponent();
/**
* 绘制子控件
*/
private void paintChildren() {
if (components.isEmpty()) {
return;
}
for (MyComponent c : components) {
c.paint();
}
}
/**
* 添加子控件
* @param component 子控件
*/
public void add(MyComponent component) {
components.add(component);
}
/**
* 添加子控件集合
* @param components 子控件集合
*/
public void add(MyComponent... components) {
for (MyComponent component : components) {
add(component);
}
}
}
// MyFrame.java
public class MyFrame extends MyContainer {
private String id;
public MyFrame(String id) {
this.id = id;
}
@Override
void paintComponent() {
System.out.printf("print Frame(%s)\n", id);
}
}
// MyLabel.java
public class MyLabel extends MyComponent {
private String text;
public MyLabel(String text) {
this.text = text;
}
@Override
void paint() {
System.out.printf("print Label(%s)\n", text);
}
}
// MyButton.java
public class MyButton extends MyComponent {
private String text;
public MyButton(String text) {
this.text = text;
}
@Override
void paint() {
System.out.printf("print Button(%s)\n", text);
}
}
// MyCheckBox.java
public class MyCheckBox extends MyComponent{
private String label;
public MyCheckBox(String label) {
this.label = label;
}
@Override
void paint() {
System.out.printf("print CheckBox(%s)\n", label);
}
}
// MyLinkLabel.java
public class MyLinkLabel extends MyComponent {
private String text;
public MyLinkLabel(String text) {
this.text = text;
}
@Override
void paint() {
System.out.printf("print LinkLabel(%s)\n", text);
}
}
// MyPasswordBox.java
public class MyPasswordBox extends MyComponent {
private String label;
public MyPasswordBox(String label) {
this.label = label;
}
@Override
void paint() {
System.out.printf("print PasswordBox(%s)\n", label);
}
}
// MyPicture.java
public class MyPicture extends MyComponent {
private String url;
public MyPicture(String url) {
this.url = url;
}
@Override
void paint() {
System.out.printf("print Picture(%s)\n", url);
}
}
// MyTextBox.java
public class MyTextBox extends MyComponent {
private String text;
public MyTextBox(String text) {
this.text = text;
}
@Override
void paint() {
System.out.printf("print TextBox(%s)\n", text);
}
}
// MyWinForm.java
public class MyWinForm extends MyContainer {
private String title;
public MyWinForm(String title) {
this.title = title;
}
@Override
void paintComponent() {
System.out.printf("print WinForm(%s)\n", title);
}
}
// Main.java
public class Main {
public static void main(String[] args) {
MyWinForm winForm = new MyWinForm("WINDOW窗口");
MyPicture picture = new MyPicture("LOGO图片");
MyButton loginButton = new MyButton("登录");
MyButton registerButton = new MyButton("注册");
MyFrame frame = new MyFrame("FRAME1");
MyLabel usernameLabel = new MyLabel("用户名");
MyTextBox textBox = new MyTextBox("文本框");
MyLabel passwordLabel = new MyLabel("密码");
MyPasswordBox passwordBox = new MyPasswordBox("密码框");
MyCheckBox multiCheckBox = new MyCheckBox("复选框");
MyTextBox rememberTextBox = new MyTextBox("记住用户名");
MyLinkLabel forgetPasswordLinkLabel = new MyLinkLabel("忘记密码");
winForm.add(picture, loginButton, registerButton, frame);
frame.add(usernameLabel, textBox, passwordLabel, passwordBox,
multiCheckBox, rememberTextBox, forgetPasswordLinkLabel);
winForm.paint();
}
}
// output:
print WinForm(WINDOW窗口)
print Picture(LOGO图片)
print Button(登录)
print Button(注册)
print Frame(FRAME1)
print Label(用户名)
print TextBox(文本框)
print Label(密码)
print PasswordBox(密码框)
print CheckBox(复选框)
print TextBox(记住用户名)
print LinkLabel(忘记密码)

类图如下所示:

用户头像

索隆

关注

还未添加个人签名 2018.04.28 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营作业3