跳转至

面向对象程序设计:语法、填空与设计模式案例

本章覆盖 C++、Java 语法要点、代码填空技巧,以及观察者和桥接两个 Java 案例。原题说明、填空代码和参考答案保留;补充解析与可运行示例单独标明。代码中的中文标点、Markdown 转义与缩进已规范化。

基础概念可参见面向对象技术总览与基础和开发流程,模式分类见设计模式复习。

C++ 语法要点

类、继承与访问控制

C++ 使用 public:、protected:、private: 划分成员访问区,类定义末尾要写分号。派生类用冒号引出基类,也可列出多个基类:class Derived : public Base1, public Base2 { ... };。class 默认成员访问和默认继承方式均为 private,struct 均为 public。

课件中的 CashNormal 公有继承 CashSuper 示例

图片展示的是 public 继承。下面的访问表为补充,列的是**基类成员在派生类中的访问级别**:

基类成员 public 继承 protected 继承 private 继承
public public protected private
protected protected protected private
private 派生类不能直接访问 派生类不能直接访问 派生类不能直接访问

private 成员并非从基类子对象中消失,派生类仍可通过基类允许访问的成员函数间接使用它。**继承方式**和**成员本身的访问修饰符**是两件事;对外表示“是一个”且允许普通调用者向上转型,通常使用公有继承。

类外定义、虚函数和纯虚函数

写法 含义与边界
double CashNormal::acceptCash(double money) :: 是作用域解析运算符,说明这个定义属于 CashNormal
virtual double acceptCash(double money); 声明虚函数;若需要其实现,还须提供函数定义,不等于抽象方法
virtual double acceptCash(double money) = 0; 声明纯虚函数;类仍有未被具体覆盖的纯虚函数时,属于抽象类
double acceptCash(double money) override; 显式要求覆盖基类虚函数,签名不匹配会报错

纯虚函数也可以有类外定义,= 0 并不等于“绝不允许定义函数体”。多态基类若会通过基类指针删除派生对象,析构函数应为虚函数。

对象、指针与引用:可运行示例

对象和引用用 .,指针用 ->。以下把课件的类定义、纯虚函数、类外定义和对象访问合为一个完整示例;原文的 retum 已改为 return。

cpp-basics.cpp
#include <cassert>
#include <iostream>

class CashSuper {
public:
    virtual ~CashSuper() = default;
    virtual double acceptCash(double money) = 0;
};

class CashNormal : public CashSuper {
public:
    double acceptCash(double money) override;
};

double CashNormal::acceptCash(double money) {
    return money;
}

int main() {
    CashNormal normal;
    CashSuper& ref = normal;
    CashSuper* ptr = &normal;
    assert(normal.acceptCash(100) == 100);
    assert(ref.acceptCash(100) == 100);
    assert(ptr->acceptCash(100) == 100);
    std::cout << "100 100 100\n";
}

课件另有 LightOffCommand *remoteControl = new LightOffCommand(kitchenLight); 后调用 remoteControl->setCommand(...) 的片段。该片段只能说明 -> 写法;没有类定义,无法确认 LightOffCommand 真有 setCommand。不要仅凭变量名 remoteControl 推断对象支持的方法。

Java 语法要点

类与接口的关系

关系或元素 Java 写法 易错点
类继承类 class Son extends Father 类只能直接继承一个父类
类实现接口 class A implements I1, I2 可实现多个接口
接口继承接口 interface C extends I1, I2 使用 extends,可继承多个接口
实例字段 private double moneyDiscount; 每个成员单独写访问修饰符,没有 C++ 的 private: 分区
构造方法赋值 this.moneyDiscount = moneyDiscount; this. 后必须有点;左边是字段,右边是参数
静态方法 Type.method() 属于类;不会按接收对象进行实例方法的动态分派
导入 import java.util.List; 导入类型名;import java.util.*; 不递归导入子包

public、abstract、final 是常见类修饰符;普通顶层类也可不写访问修饰符,表示包访问。abstract 与 final 不能同时用于同一个类。

课件的 CashDiscount implements CashSuper 要求 CashSuper 是接口。这与 C++ 小节中的同名抽象类是不同语言的独立示例。Java 的可编译补全如下:

CashSyntaxDemo.java
interface CashSuper {
    double acceptCash(double money);
}

class CashDiscount implements CashSuper {
    private final double moneyDiscount;

    public CashDiscount(double moneyDiscount) {
        this.moneyDiscount = moneyDiscount;
    }

    @Override
    public double acceptCash(double money) {
        return money * moneyDiscount;
    }
}

public class CashSyntaxDemo {
    public static void main(String[] args) {
        CashSuper cash = new CashDiscount(0.8);
        System.out.println(cash.acceptCash(100)); // 80.0
    }
}

抽象类、继承和装饰器示例

课件填空:(1) class Beverage { ... public abstract int cost(); }。

课件中的 Java 继承与抽象方法填空

参考答案与补充解析

原参考答案:

答案为 abstract。

补充解析:

  • 抽象方法没有方法体,以分号结束。普通类中声明了抽象方法,该类必须声明为抽象类;接口是另一种类型声明。
  • 抽象类不一定有抽象方法,也不能直接实例化。
  • 具体类必须具有所有待实现抽象方法的有效实现;可以继承已有实现,不一定亲自重写。抽象子类可以继续保留抽象方法。
  • 覆写不能降低可见性;接口的公共抽象方法必须用 public 实现。

图片中的三个位置应分别补 extends Department、abstract、public void draw();Shape 中的 abstract public void draw() 后还须补分号。下面是可编译整理:

InheritanceSyntax.java
class Department {}
class SqlserverDepartment extends Department {}

abstract class Shape {
    public abstract void draw();
}

class Rectangle extends Shape {
    @Override
    public void draw() {}
}

课件的 Whip extends CondimentDecorator 表示奶泡装饰器:保存被包装饮料,描述追加 ", Whip",价格增加 8。beverage 必须已经在本类或祖先类中声明,并可访问。以下补齐上下文;让抽象父类用构造方法接收对象,更容易看清依赖:

DecoratorSyntaxDemo.java
abstract class Beverage {
    String description = "Unknown Beverage";
    public String getDescription() { return description; }
    public abstract int cost();
}

abstract class CondimentDecorator extends Beverage {
    protected final Beverage beverage;
    protected CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }
}

class Whip extends CondimentDecorator {
    private static final int WHIP_PRICE = 8;
    public Whip(Beverage beverage) { super(beverage); }
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Whip";
    }
    @Override
    public int cost() { return WHIP_PRICE + beverage.cost(); }
}

public class DecoratorSyntaxDemo {
    public static void main(String[] args) {
        Beverage coffee = new Beverage() {
            { description = "Coffee"; }
            @Override public int cost() { return 12; }
        };
        Beverage drink = new Whip(coffee);
        System.out.println(drink.getDescription() + ": " + drink.cost());
        // Coffee, Whip: 20
    }
}

接口的定义与填空

接口字段隐含 public static final,必须初始化;普通无方法体的方法隐含 public abstract。但“接口里所有方法都是抽象方法”已经不准确:

接口方法 版本 方法体与访问
普通抽象方法 早期 Java 即支持 无方法体,隐含 public abstract
default 方法 Java 8 起 有方法体,是实例方法;可由实现类覆盖
static 方法 Java 8 起 有方法体,通过接口名调用,不由实现类继承
private / private static 方法 Java 9 起 有方法体,仅用于接口内部复用,不由实现类继承

原题:根据实现类补全接口。

Drawing-interface-exercise.txt
(1) Drawing {
    (2);
    (3);
}
class V1Drawing implements Drawing {
    public void drawLine(double x1, double y1, double x2, double y2) {
        /* 代码省略 */
    }
    public void drawCircle(double x, double y, double r) {
        /* 代码省略 */
    }
}
原参考答案与推导
  1. interface
  2. void drawLine(double x1, double y1, double x2, double y2)
  3. void drawCircle(double x, double y, double r)

implements Drawing 先确定 Drawing 是接口,再由实现类反推方法名、返回类型与参数列表。接口中可省略 public abstract,实现类的方法不能省略 public。空后已经有分号,不要重复填写。

设计模式代码填空技巧

原课件的 12 条提示整理如下;关键词用于缩小范围,最后仍须验证类型、签名与调用链。

线索 如何落到代码上
继承、实现 类继承用 extends,类实现接口用 implements;接口继承接口也用 extends
抽象方法 方法末尾为 ;,没有 {};不要把普通空方法体当成抽象方法
类中含抽象方法 类声明要有 abstract;接口还可含 default/static/private 的具体方法
父类型和子类型 由声明反推实现的签名,也由实现反推接口;抽象子类未必实现全部方法
static 看调用点是否为 类名.方法名(...);静态方法不能直接使用 this
大小写 drawCircle、drawcircle、draw_a_circle 是不同名字,不能凭语义替换
构造器里的字段赋值 先查本类,再查可访问的继承字段;若都没有,则代码不完整或有错,不代表“父类肯定有”
this 表示当前对象;既可区分字段和参数,也可作为实参,例如注册自己 registerObserver(this)
方法体内的空 查调用对象是参数、字段还是 this;同时核对参数、返回值和权限
UML 可见性 + public,# protected,- private;Java 包访问常记为 ~
代入数据 从入口顺序跟踪字段、分支、循环与返回值;检查 null 和未初始化对象
模式结构 熟悉 23 种模式的意图、角色与协作;用类图和调用链推导,避免只背类名

遇到 new 先找构造器,遇到 super(...) 先找父类签名,遇到 obj.method(...) 先查 obj 的声明类型,再确认运行时对象的覆盖实现。

用一个独立例子追踪对象与委托

类图里的“持有一个接口引用”落到代码通常是字段,初始化在构造器,业务方法再通过该字段委托。以下例子用不同策略计算配送费,单独保存为 DeliveryDemo.java 可运行:

DeliveryDemo.java
interface FeePolicy {
    int fee(int weight);
}

class PerUnitFee implements FeePolicy {
    public int fee(int weight) {
        return weight * 3;
    }
}

class Delivery {
    private final FeePolicy policy;

    Delivery(FeePolicy policy) {
        this.policy = policy;
    }

    int quote(int weight) {
        return policy.fee(weight);
    }
}

public class DeliveryDemo {
    public static void main(String[] args) {
        FeePolicy selected = new PerUnitFee();
        Delivery delivery = new Delivery(selected);
        System.out.println(delivery.quote(4)); // 12
    }
}

从入口依次画出 delivery → policy → PerUnitFee 对象。selected、构造参数 policy 和字段 this.policy 最终引用同一个策略对象,赋值不会自动复制该对象。quote(4) 把参数传给接收者的 fee(4),再把返回值交回调用方。

如果把构造器写成 policy = policy,赋值的是参数自身;此例的 final 字段没有初始化,编译就会失败。若去掉 final,字段会保留默认值 null,调用时失败。因此填空时既要检查类型能否通过编译,也要检查字段是否确实引用了预期对象。示例假设传入非空策略、合法重量,工程代码可另外校验输入。

这个例子的变化点是“同一种计算任务采用不同算法”,对应策略模式。相同的“字段+委托”结构也可能出现在桥接、状态、装饰器等模式中;还须看它是两套独立层次、状态驱动行为变化,还是对同一接口逐层包装。各模式角色及对比见设计模式复习。

案例一:实验室环境监测

原题说明

阅读下列说明和 Java 代码,将应填入 (n) 处的字句写在对应栏内。

某实验室欲建立一个实验室环境监测系统,能够显示实验室的温度、湿度以及洁净度等环境数据。当获取到最新的环境测量数据时,显示的环境数据能够更新。

现在采用观察者(Observer)模式来开发该系统。观察者模式的类图如下。

实验室环境监测题给出的观察者模式类图

原题填空代码

保留题目的原始类型和省略部分;本块是待填练习,不能直接编译。

Observer-exercise.txt
import java.util.*;
interface Observer {
    public void update(float temp, float humidity, float cleanness);
}
interface Subject {
    public void registerObserver(Observer o); //注册对主题感兴趣的观察者
    public void removeObserver(Observer o); //删除观察者
    public void notifyObservers(); //当主题发生变化时通知观察者
}
class EnvironmentData implements (1) {
    private ArrayList observers;
    private float temperature, humidity, cleanness;
    public EnvironmentData() { observers = new ArrayList(); }
    public void registerObserver(Observer o) { observers.add(o); }
    public void removeObserver(Observer o) { /* 代码省略 */ }
    public void notifyObservers() {
        for (int i = 0; i < observers.size(); i++) {
            Observer observer = (Observer)observers.get(i);
            (2);
        }
    }
    public void measurementsChanged() { (3); }
    public void setMeasurements(float temperature, float humidity, float cleanness) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.cleanness = cleanness;
        (4);
    }
}
class CurrentConditionsDisplay implements (5) {
    private float temperature;
    private float humidity;
    private float cleanness;
    private Subject envData;
    public CurrentConditionsDisplay(Subject envData) {
        this.envData = envData;
        (6);
    }
    public void update(float temperature, float humidity, float cleanness) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.cleanness = cleanness;
        display();
    }
    public void display() {/* 代码省略 */ }
}
class EnvironmentMonitor{
    public static void main(String[] args) {
        EnvironmentData envData = new EnvironmentData();
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(envData);
        envData.setMeasurements(80, 65, 30.4f);
    }
}
参考答案与补充解析

案例一:实验室环境监测——观察者模式

原参考答案与推导

六个空的答案

空 原参考答案 推导
1 Subject EnvironmentData 提供注册、删除、通知方法,是具体主题
2 observer.update(temperature, humidity, cleanness) 逐一通知已注册观察者,推送三个最新值
3 notifyObservers() “测量发生变化”触发通知
4 measurementsChanged() 先更新字段,再进入变化通知流程
5 Observer 显示对象实现 update,属于观察者
6 envData.registerObserver(this) 构造显示对象时,把当前显示对象注册到主题

调用链:setMeasurements → measurementsChanged → notifyObservers → observer.update → display。空 6 漏掉注册时,主题的列表为空,即使测量变化也不会通知这个显示对象。

模式角色与读图边界

观察者是行为型模式,定义对象间的一对多依赖:主题状态变化时,通知相关观察者,使其更新。

模式角色 本题对象或类型 职责
Subject Subject 定义注册、移除和通知接口
ConcreteSubject EnvironmentData 存储测量数据及观察者列表,变化时发送通知
Observer Observer 定义更新接口
ConcreteObserver CurrentConditionsDisplay 接收最新数据并显示,持有主题引用以注册或取消注册

课件中的观察者通用结构与取状态过程

通用图用 Update() 通知,再从 subject.GetState() 拉取数据;本题代码用带参数的 update(...) 直接推送数据。两种协作都属于观察者模式。具体观察者不一定必须依赖具体主题类型,本题依赖的是 Subject 接口。

原图有 ObServer、Observe、notifyObserver 等拼写差异,填代码以声明的 Observer、notifyObservers 为准。若将两个父类型画成 Java 接口,UML 实现关系应使用虚线加空心三角。图上两端 0..* 允许一个观察者订阅多个主题;题中一个显示对象只有一个 envData 字段,是这道题的实现选择。

补充:可编译运行版本

原答案补全后能够编译,但原始 ArrayList 会产生类型检查警告,removeObserver 与 display 仍为空。下面采用泛型并补齐这两处,保留题目的通知链。示例只处理单线程下的基本订阅,不增加异步或并发语义。

ObserverDemo.java
import java.util.ArrayList;
import java.util.List;

interface Observer {
    void update(float temp, float humidity, float cleanness);
}

interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

class EnvironmentData implements Subject {
    private final List<Observer> observers = new ArrayList<>();
    private float temperature, humidity, cleanness;

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, cleanness);
        }
    }
    public void measurementsChanged() { notifyObservers(); }
    public void setMeasurements(float temperature, float humidity, float cleanness) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.cleanness = cleanness;
        measurementsChanged();
    }
}

class CurrentConditionsDisplay implements Observer {
    private float temperature, humidity, cleanness;
    private final Subject envData;

    public CurrentConditionsDisplay(Subject envData) {
        this.envData = envData;
        envData.registerObserver(this);
    }
    @Override
    public void update(float temperature, float humidity, float cleanness) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.cleanness = cleanness;
        display();
    }
    public void display() {
        System.out.println(temperature + ", " + humidity + ", " + cleanness);
    }
    public void close() { envData.removeObserver(this); }
}

public class ObserverDemo {
    public static void main(String[] args) {
        EnvironmentData envData = new EnvironmentData();
        CurrentConditionsDisplay display = new CurrentConditionsDisplay(envData);
        envData.setMeasurements(80, 65, 30.4f);
        display.close();
        envData.setMeasurements(20, 40, 50); // 已取消订阅,不再打印
    }
}

输出只有一行:80.0, 65.0, 30.4。题干没有给出三个量的单位,不能把 80 擅自解释为某种温标。关键词是“状态变化”“通知多个对象”“注册/取消”,应对应注册列表与更新调用,而不是只看是否出现 update 这个名字。延伸阅读:观察者模式。

案例二:绘图软件

原题说明

阅读下列说明和 Java 代码,将应填入空 (n) 处的字句写在对应栏内。

欲开发一个绘图软件,要求使用不同的绘图程序绘制不同的图形。以绘制直线和圆形为例,对应的绘图程序如下表。

课件表6-1:DP1和DP2绘图函数及参数

绘图操作 DP1(原表) DP2(原表)
直线 draw_a_line(x1, y1, x2, y2) drawline(x1, x2, y1, y2)
圆 draw_a_circle(x, y, r) drawcircle(x, y, r)

该绘图软件的扩展性要求:将不断扩充新的图形和新的绘图程序。为了避免出现类爆炸的情况,现采用桥接(Bridge)模式实现,得到如下类图。

Shape与Drawing两套层次通过对象引用连接的桥接类图

原题填空代码

保留全部原题类与省略位置;省略的构造器内容尚未补齐。

Bridge-exercise.txt
(1) Drawing{
    (2);
    (3);
}
class DP1{
    static public void draw_a_line(double x1, double y1, double x2, double y2) {/*代码省略*/}
    static public void draw_a_circle(double x, double y, double r) {/*代码省略*/}
}
class DP2{
    static public void drawline(double x1, double y1, double x2,double y2){/*代码省略*/}
    static public void drawcircle (double x, double y, double r){/*代码省略*/}
}
class V1Drawing implements Drawing{
    public void drawLine(double x1, double y1, double x2,double y2){/*代码省略*/}
    public void drawCircle(double x, double y, double r){
        (4);
    }
}
class V2Drawing implements Drawing{
    public void drawLine(double x1, double y1, double x2,double y2){/*代码省略*/}
    public void drawCircle (double x, double y, double r){ (5);}
}
abstract class Shape{
    private Drawing _dp;
    (6);
    Shape(Drawing dp) {_dp=dp;}
    public void drawLine(double x1, double y1, double x2,double y2){_dp.drawLine(x1,y1,x2,y2); }
    public void drawCircle (double x, double y, double r){ _dp.drawCircle(x,y,r);}
}
class Rectangle extends Shape{
    private double _x1,_x2,_y1,_y2;
    public Rectangle(Drawing dp,double x1, double y1, double x2,double y2) {/*代码省略*/}
    public void draw(){/*代码省略*/}
}
class Circle extends Shape{
    private double _x, _y, _r;
    public Circle(Drawing dp,double x, double y, double r) {/*代码省略*/}
    public void draw(){drawCircle(_x,_y,_r);}
}
参考答案与补充解析

案例二:绘图软件——桥接模式

原表与代码存在不一致: 原表 DP2 直线参数顺序为 (x1, x2, y1, y2),下面原代码的声明为 (x1, y1, x2, y2)。本题的 6 个填空不涉及这处直线调用。后面的可运行版本按原代码声明统一;若实际库遵循原表,则必须在 V2Drawing.drawLine 内重排参数。所有参数都是 double,编译器无法替你发现这种语义错位。

原参考答案与推导

六个空的答案

空 原参考答案 推导
1 public interface 或 interface V1Drawing、V2Drawing 均 implements Drawing
2 public void drawLine(double x1, double y1, double x2, double y2) 或省略 public 从实现类反推接口签名
3 public void drawCircle(double x, double y, double r) 或省略 public 同上
4 DP1.draw_a_circle(x, y, r) V1 封装 DP1 的静态圆绘制函数
5 DP2.drawcircle(x, y, r) V2 封装 DP2,方法名大小写必须一致
6 abstract public void draw() 各具体图形实现自己的绘制流程;也可写 public abstract void draw()

空后原有分号,答案不必再加。若空 1 使用 public interface,须将该顶层公共接口放在 Drawing.java 中;把全部示例放进一个演示文件时可选择原答案允许的 interface。

为什么是桥接:找两个独立变化的维度

本题两个维度是:

  • 图形种类:Shape → Rectangle / Circle,决定图形如何由线、圆等操作组成。
  • 绘图实现:Drawing → V1Drawing / V2Drawing,决定这些操作最终调用哪套绘图库。

Shape 持有 Drawing 引用,就是连接两个维度的“桥”。例如 new Circle(new V2Drawing(), ...) 表示“圆形 × DP2”,换成 new V1Drawing() 就可组合出“圆形 × DP1”。图中的矩形会调用四次直线绘制;直线在这份代码中是绘制操作,没有独立的 Line 图形类。

若为每种组合各建一个类,m 种图形与 n 套实现可能需要 m×n 个组合类;桥接让两套层次分别扩展,主体通常是 m+n 个具体类型。新增 Triangle 只须遵循已有绘图能力;新增 V3Drawing 则实现现有 Drawing。如果新增图形需要接口尚未提供的操作,仍可能需要调整接口,不能理解为所有扩展都绝不修改旧代码。

关键词“两个维度独立扩展”“避免类爆炸”提示桥接。V1/V2 封装已有库时也发挥适配作用,但整个设计要解决的是图形与绘图实现的独立变化。延伸阅读:桥接模式。

补充:可编译运行版本

只填六个空仍不能把原题直接当作完整程序编译:Shape 只有带参数构造器,Rectangle 和 Circle 的省略部分必须调用 super(dp) 并初始化坐标。下面同时补齐直线转发、矩形四边、圆形绘制与运行入口,用打印代替真实绘图库。

BridgeDemo.java
interface Drawing {
    void drawLine(double x1, double y1, double x2, double y2);
    void drawCircle(double x, double y, double r);
}

class DP1 {
    public static void draw_a_line(double x1, double y1, double x2, double y2) {
        System.out.println("DP1 line " + x1 + "," + y1 + " -> " + x2 + "," + y2);
    }
    public static void draw_a_circle(double x, double y, double r) {
        System.out.println("DP1 circle " + x + "," + y + " r=" + r);
    }
}

class DP2 {
    public static void drawline(double x1, double y1, double x2, double y2) {
        System.out.println("DP2 line " + x1 + "," + y1 + " -> " + x2 + "," + y2);
    }
    public static void drawcircle(double x, double y, double r) {
        System.out.println("DP2 circle " + x + "," + y + " r=" + r);
    }
}

class V1Drawing implements Drawing {
    @Override
    public void drawLine(double x1, double y1, double x2, double y2) {
        DP1.draw_a_line(x1, y1, x2, y2);
    }
    @Override
    public void drawCircle(double x, double y, double r) {
        DP1.draw_a_circle(x, y, r);
    }
}

class V2Drawing implements Drawing {
    @Override
    public void drawLine(double x1, double y1, double x2, double y2) {
        DP2.drawline(x1, y1, x2, y2);
    }
    @Override
    public void drawCircle(double x, double y, double r) {
        DP2.drawcircle(x, y, r);
    }
}

abstract class Shape {
    private final Drawing dp;
    protected Shape(Drawing dp) { this.dp = dp; }
    public abstract void draw();
    public void drawLine(double x1, double y1, double x2, double y2) {
        dp.drawLine(x1, y1, x2, y2);
    }
    public void drawCircle(double x, double y, double r) {
        dp.drawCircle(x, y, r);
    }
}

class Rectangle extends Shape {
    private final double x1, y1, x2, y2;
    public Rectangle(Drawing dp, double x1, double y1, double x2, double y2) {
        super(dp);
        this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2;
    }
    @Override
    public void draw() {
        drawLine(x1, y1, x2, y1);
        drawLine(x2, y1, x2, y2);
        drawLine(x2, y2, x1, y2);
        drawLine(x1, y2, x1, y1);
    }
}

class Circle extends Shape {
    private final double x, y, r;
    public Circle(Drawing dp, double x, double y, double r) {
        super(dp);
        this.x = x; this.y = y; this.r = r;
    }
    @Override
    public void draw() { drawCircle(x, y, r); }
}

public class BridgeDemo {
    public static void main(String[] args) {
        for (Drawing drawing : new Drawing[]{new V1Drawing(), new V2Drawing()}) {
            new Rectangle(drawing, 1, 2, 4, 6).draw();
            new Circle(drawing, 3, 5, 2).draw();
        }
    }
}

运行会打印 10 行:每套绘图实现各绘制 4 条矩形边和 1 个圆。例如第一行是 DP1 line 1.0,2.0 -> 4.0,2.0,最后一行是 DP2 circle 3.0,5.0 r=2.0。Shape 子类只决定“画什么”,V1/V2 决定“用哪套程序画”。

复习自检

  1. 能否从 implements 反推出接口,再从具体类补全接口签名?
  2. 能否逐步追踪观察者的注册和通知链,并解释取消注册后为何不再显示?
  3. 能否指出桥接中两个独立变化的维度,以及持有对方对象的那条关系?
  4. 填空后是否同时满足访问权限、方法签名、构造器调用与参数语义?

文中的 Java 示例按**不同文件、不同练习目录**分别编译,避免独立示例里的 Shape、Drawing 等同名类型相互冲突。

历年 Java 大题练习

本节按年份由近到远收录 18 道完整历年大题。先展开题干独立作答,再展开其中的答案与解析;平台答案与整理补充继续分别保留。

题目复用题库中的同一份正文,题图使用本地文件。普通 Markdown 阅读器可通过原题链接进入;完整性与剩余试卷见大题整理进度。

程序设计选做题只收 Java,不将 C++ 版本重复加入本节。原卷的 Java 题号有时是第 5 题,有时是第 6 题,按题干语言识别,不按题号猜测。

2024年下半年 · 第 6 题:Java 图形编辑器的组合模式填空

展开原题(先作答,再查看解析)

Java 图形编辑器的组合模式填空

(六)(15分)

【说明】

某图形编辑器能够处理不同类型的图形,包括基本图形(如圆形、矩形等)和复合图形(由多个基本图形组合而成)。在该编辑器中用户能够以统一的方式操作单个图形和复合图形,例如,能够对它们进行绘制、移动和删除等操作。现采用组合(Composite)模式实现上述要求,得到如图1所示的类图。

【Java代码】

import java.util.ArrayList;
import java.util.*;
// 抽象图形元素父类
abstract class GraphicElement {
    protected (1);
    public (2);
    public (3);
}
// 基本图形:圆形
class Circle extends GraphicElement {
    private int radius;
    public Circle(String name, int radius) {
        this.name = name;
        this.radius = radius;
    }
    public void draw() {
        System.out.println("Drawing Circle:" + name);
    }
    public void move(int dx, int dy) {
        System.out.println("Moving Circle:" + name + "by(" + dx + "," + dy + ")");
    }
}
// 复合图形
class CompositeGraphic extends GraphicElement {
    private (4);
    public CompositeGraphic(String name) {
        this.name = name;
        elements = new ArrayList
    }
    public void add(GraphicElement elem) {
        elements.add(elem);
    }
    public void remove(GraphicElement elem) {
        elements.remove(elem);
    }
    public void draw() {
        System.out.println("Drawing CompositeGraphic:" + name);
    }
    public void move(int dx, int dy) {
        System.out.println("Drawing CompositeGraphic:" + name + "by(" + dx + "," + dy + ")");
    }
}
// 测试类
class CompositePatternTest {
    public static void main(String[] args) {
        (5);
        Circle circle1 = new Circle("Circle1", 10);
        Circle circle2 = new Circle("Circle2", 20);
        graphic.add(circle1);
        graphic.add(circle2);
        graphic.draw();
        graphic.move(5,5);
    }
}

01(15分)
阅读说明和Java代码,将应填入(1)~(5)处的程序补充完整。

答案与解析

平台参考答案(原意保留)

平台参考答案(保留原始表达):

  1. String name;
  2. abstract void draw();
  3. abstract void move(int dx, int dy);
  4. List<GraphicElement> elements; 或 ArrayList<GraphicElement> elements
  5. CompositeGraphic graphic = new CompositeGraphic(“Group1”);

上述中文弯引号及分号是平台答案中的写法,不代表可不加处理地原样粘贴编译。

解析与纠错

解析与纠错:

  • GraphicElement 用抽象方法约定操作,Circle 与 CompositeGraphic 通过继承获得共同接口,List/ArrayList 存放 GraphicElement 引用。若按题干自带的分号填空,可填写:String name、abstract void draw()、abstract void move(int dx, int dy)、List<GraphicElement> elements、CompositeGraphic graphic = new CompositeGraphic("Group1")。声明处使用 List 或 ArrayList 均可与 ArrayList 实例兼容,前者较少依赖具体实现。
  • 原题构造函数只显示 elements = new ArrayList。原页已确认这一缺损,原题缺少 <>();。仅填写五空无法让这个代码片段通过 Java 编译。完整修正版使用 elements = new ArrayList<>();。这是额外的语法修复,不是某一空的答案。
  • 平台(5)的中文弯引号 “ ” 不是 Java 字符串定界符;代码中需使用 ASCII 双引号。Group1 只是对象名称,题干并未规定一定要使用它。平台答案部分空带分号,题干又自带分号,填入时可去掉重复的末尾分号;多出的空语句不构成此次核心故障。
  • 与 C++ 题一样,原始 draw/move 只打印复合对象,没有调用子组件,不能完成组合操作的传播。只修复构造语句后,子节点调用探针依然失败。应遍历 elements,分别调用每个子对象的 draw 或 move;嵌套组合也会由多态调用继续向下传递。
  • 原始 move 的日志文案也写成了 Drawing CompositeGraphic,完整示例改为 Moving CompositeGraphic。remove 只从集合解除引用,Java 的垃圾回收不等于立即销毁对象;题干未给出额外资源释放协议。示例仍用日志表示绘制/移动,不声称实现了真实坐标变化。

Java 21 编译和测试通过:空组合、基类引用调用、直接与嵌套子节点、移动参数传递、移除叶子/子组后停止调用。

补充与完整修正示例

完整修正示例:

示例补齐构造语句、委派子组件操作并修正移动日志。列表不得包含 null,组合结构不得形成环。代码文件名为 CompositePatternTest.java。

import java.util.ArrayList;
import java.util.List;

abstract class GraphicElement {
    protected String name;
    public abstract void draw();
    public abstract void move(int dx, int dy);
}

class Circle extends GraphicElement {
    private final int radius;
    public Circle(String name, int radius) { this.name = name; this.radius = radius; }
    @Override public void draw() { System.out.println("Drawing Circle:" + name); }
    @Override public void move(int dx, int dy) {
        System.out.println("Moving Circle:" + name + " by (" + dx + "," + dy + ")");
    }
}

class CompositeGraphic extends GraphicElement {
    private final List<GraphicElement> elements;
    public CompositeGraphic(String name) {
        this.name = name;
        elements = new ArrayList<>();
    }
    public void add(GraphicElement elem) { elements.add(elem); }
    public void remove(GraphicElement elem) { elements.remove(elem); }
    @Override public void draw() {
        System.out.println("Drawing CompositeGraphic:" + name);
        for (GraphicElement elem : elements) elem.draw();
    }
    @Override public void move(int dx, int dy) {
        System.out.println("Moving CompositeGraphic:" + name + " by (" + dx + "," + dy + ")");
        for (GraphicElement elem : elements) elem.move(dx, dy);
    }
}

public class CompositePatternTest {
    public static void main(String[] args) {
        CompositeGraphic graphic = new CompositeGraphic("Group1");
        Circle circle1 = new Circle("Circle1", 10), circle2 = new Circle("Circle2", 20);
        graphic.add(circle1);
        graphic.add(circle2);
        graphic.draw();
        graphic.move(5, 5);
    }
}

2024年上半年 · 第 6 题:Java 银行账户状态模式与操作委派

展开原题(先作答,再查看解析)

Java 银行账户状态模式与操作委派

阅读下列说明和Java代码,回答问题。
在银行账户管理系统中,账户可以处于多种状态,如激活、冻结、注销等。在每个不同的状态下,都有特定的行为,例如激活状态的账户可以进行存款和取款操作,而冻结状态的账户则不能进行任何操作。现采用State(状态)模式实现上述场景,得到如图1所示的类图。

【Java代码】

interface AccountState {
void deposit(double amount);
void withdraw(double amount);
void freeze();
void unfreeze();
}
class Account {
private (1);
public Account() {
this.state = null;
}
public void setState(AccountState state) {
this.state = state;
}
public void deposit(double amount) {
(2);
}
public void withdraw(double amount) {
(3);
}
public void freeze() {
(4);
}
public void unfreeze() {
(5);
}
}
class ActiveState implements AccountState {
public void deposit(double amount) {
System.out.println("Depositing" + amount + "into the account.");
}
public void withdraw(double amount) {
System.out.println("Withdrawing " + amount + "from the account.");
}
public void freeze() {
System.out.println("Account has been frozen.");
}
public void unfreeze() {
System.out.println("Cannot unfreeze an active account.");
}
}
class FrozenState implements AccountState {
public void deposit(double amount) {
System.out.println("Cannot deposit into a frozen account.");
}
public void freeze() {
System.out.println("Account is already frozen.");
}
public void unfreeze() {
System.out.println("Account has been unfrozen.");
}
}

01(15分)

根据以上说明和Java代码,填充Java代码中的空(1)~(5)。

答案与解析

平台参考答案(原意保留)

平台参考答案:

  1. AccountState state
  2. state.deposit(amount)
  3. state.withdraw(amount)
  4. state.freeze()
  5. state.unfreeze()

解析与纠错

解析与纠错:

  • 五个填空体现状态模式的委派:Account保存AccountState引用,四种操作转交给当前状态。平台填空与现有接口一致。
  • FrozenState 声明实现AccountState,但没有实现 withdraw(double amount),且并非抽象类,因此填完五空后仍不能编译。冻结状态的实现至少还应提供该方法并拒绝取款。
  • Account构造函数把state设为null,立即调用四种委派操作会抛出NullPointerException。可以要求使用前设置有效状态,也可以定义一个明确的初始状态;参考实现约定新账户初始处于激活状态。
  • 原来的 ActiveState.freeze 和 FrozenState.unfreeze 只打印消息,没有调用setState。即使先给账户设置ActiveState,调用freeze后仍然允许执行激活状态的存款逻辑。要产生状态转换,可让具体状态持有Account上下文,在冻结时切换为FrozenState,在解冻时切换为ActiveState。
  • 激活状态的重复解冻和冻结状态的重复冻结可保持原状态并提示;冻结状态拒绝存取款。说明中虽然还提到注销,但给定接口、类图没有提供注销方法和对应状态,不能将其当作五空已有的行为。
  • 参考实现仅用事件记录演示操作和状态转换,没有实现真实余额、金额校验或账务事务。

Java 21 编译和测试通过:有效初始状态、存取款委派、实际冻结/解冻转换、冻结期间拒绝存取款、重复转换、拒绝null状态。

补充与完整参考实现

完整参考实现:

保留原有四种操作,补齐冻结状态的取款方法,并通过上下文实现状态切换。事件列表用于观察状态行为;新账户初始激活是此示例的明确约定。代码文件名为 BankStateDemo.java。

import java.util.ArrayList;
import java.util.List;

interface AccountState {
    void deposit(double amount);
    void withdraw(double amount);
    void freeze();
    void unfreeze();
}

class Account {
    private AccountState state;
    private final List<String> events = new ArrayList<>();
    public Account() { state = new ActiveState(this); }
    public void setState(AccountState state) {
        if (state == null) throw new IllegalArgumentException("state must not be null");
        this.state = state;
    }
    public void deposit(double amount) { state.deposit(amount); }
    public void withdraw(double amount) { state.withdraw(amount); }
    public void freeze() { state.freeze(); }
    public void unfreeze() { state.unfreeze(); }
    void record(String event) { events.add(event); System.out.println(event); }
    public String stateName() { return state.getClass().getSimpleName(); }
    public List<String> events() { return List.copyOf(events); }
}

class ActiveState implements AccountState {
    private final Account account;
    public ActiveState(Account account) { this.account = account; }
    public void deposit(double amount) { account.record("deposit:" + amount); }
    public void withdraw(double amount) { account.record("withdraw:" + amount); }
    public void freeze() {
        account.setState(new FrozenState(account));
        account.record("frozen");
    }
    public void unfreeze() { account.record("already-active"); }
}

class FrozenState implements AccountState {
    private final Account account;
    public FrozenState(Account account) { this.account = account; }
    public void deposit(double amount) { account.record("deposit-rejected:" + amount); }
    public void withdraw(double amount) { account.record("withdraw-rejected:" + amount); }
    public void freeze() { account.record("already-frozen"); }
    public void unfreeze() {
        account.setState(new ActiveState(account));
        account.record("unfrozen");
    }
}

public class BankStateDemo {
    public static void main(String[] args) {
        Account account = new Account();
        account.deposit(100);
        account.freeze();
        account.withdraw(10);
        account.unfreeze();
        account.withdraw(10);
    }
}

2023年下半年 · 第 5 题:公告栏系统的命令模式填空

展开原题(先作答,再查看解析)

公告栏系统的命令模式填空

【考生回忆版】

某公司开发公告栏系统,通过菜单执行创建、打开、编辑公告栏等操作。系统采用Command(命令)模式,类图如下。

图1 命令模式类图

【Java代码】

import java util*;  
(1) class Command {  
(2);  
public (3);  
}  
class createCommand extends Command {  
public createCommand() { bs = new BoardScreen();}  
public void execute() { bs.create();}  
}  
class openCommand extends Command (  
public openCommand() ( bs = new BoardScreen();)  
public void execute()( bs.open();)

}

class openCommand extends Command (  
public openCommand() ( bs = new BoardScreen();)  
public void execute()( bs.edit();)

}

class BoardScreen{  
public void open() ( System. out println("open BoardScreen'), )  
public void create() {System.out.println("create BoardScree").}  
public void edit() {System.out.println("edit BoardScree");}

}

class Menultem {  
private Command command;  
private String itemName;  
public Menultem(Command command, String itemName) {  
this.command = command;  
this.itemName = itemName;  
}  
public String getltemName(){return itemName;}  
public void click(){  
System.out.println("Menultem "+ this.itemName + " is excuting);  
(4)

}

}

class Menu {  
private ArrayList < Menultem > MenultemsList = new ArrayList < Menultem > 0;  
public void addMenultem(Menultem item) ( MenultemsList.add(item);)  
public void runMenultem(String itemName) {  
for (int i= 0; i < MenultemsList.size(), i+ +) {  
if(MenulternsList get(i)getItemName().equals (itemName)) {  
MenultemsList.get(i).(5);  
return;

}

}

}

}

根据说明和Java代码,填写(1)~(5)。

答案与解析

平台参考答案(原文保留)

1、public abstract
2、BoardScreen bs;
3、abstract void execute();
4、command.execute();
5、click()

解析与纠错

五处填空的职责与平台答案一致:

  1. public abstract:Command包含抽象操作,必须为抽象类;public是平台采用的可见性。
  2. BoardScreen bs:保存实际执行公告栏操作的接收者,具体命令构造器为它赋值。
  3. abstract void execute():定义统一操作;题干在该空前已有public。
  4. command.execute();:菜单项作为调用者,点击时委托给其持有的命令。
  5. click():菜单按名称找到菜单项后,触发该项的点击操作。

题干在(2)、(3)后已给分号,填写时可不再写;平台答案多带一个分号形成类体空声明,并非本题主要错误。创建、打开、编辑命令分别把execute转交给BoardScreen的create、open、edit,因此新增操作时可新增具体命令,而不在菜单项里集中判断每种业务操作。

原站可见页面已核实:两个实现类及其构造器均名为openCommand,第二个却调用bs.edit;图中另有editCommand。import java util*;、方法体括号混用、println前缺点、单双引号不配对、create语句结尾为.}、click的is excuting字符串未闭合、ArrayList构造以字符0结尾、if表达式中的拼写与成员访问符号错误,均为原题问题。Menu类结束后直接进入填空说明,未漏后续代码;全部五空可见。getItemName的I/l字形无法仅凭截图完全区分,上方保留原题代码的写法。五空填全后原片段仍不能编译。

补充说明

整理修正时,应将导入改为import java.util.*;,第二个openCommand及其构造器改为editCommand,并修复花括号、点、分号、字符串引号和new ArrayList<...>()。菜单项类、列表变量及访问方法须统一拼写,方法统一为getItemName(),再使用列表的get(i)取得菜单项并比较其名称。这些是依据类图和Java语法作出的整理修正,不是原页代码;不改动上方原题片段或平台答案。

2023年上半年 · 第 5 题:区间显示程序(Java)

展开原题(先作答,再查看解析)

区间显示程序(Java)

阅读下列说明和Java代码,将应填入 (n) 处的字句写在答题纸的对应栏内。
【说明】
在某系统中,类Interval代表由下界(lower bound)和上界 (upper bound) 定义的区间,要求采用不同的格式显示区间范围,如[lower bound,upper bound]、[lower bound…upper bound]、[lower bound - upper bound]等。现采用策略 (strategy) 模式实现该要求,得到如图5-1所示的类图。

【Java代码】

import java.util.*;

enum TYPE { COMMA, DOTS, LINE};

interface PrintStrategy {
public (1);

}

class Interval {
private double lowerBound;
private double upperBound;
public Interval(double p_lower, double p_upper) {
lowerBound = p_lower; upperBound = p_upper;

}
public void printInterval( PrintStrategy ptr ) {
(2);

}
public double getLower() { retun lowerBound; }
public double getUpper() { return upperBound; }

}

class PrintIntervalsComma implements PrintStrategy {
public void doPrint(Interval val) {
System.out.println("[" + val.getLower() + "," + val.getUpper() + "]");

}

}

class PrintIntervalsDots implements PrintStrategy{
public void doPrint(Interval val) {
System.out.println("[" + val.getLower() +"…" + val.getUpper() + "]");

}

}

class PrintIntervalsLine implements PrintStrategy {
public void doPrint(Interval val) {
System.out.println("[" + val.getLower() +"-" + val.getUpper() + "]");

}

}

class Strategy{
public static PrintStrategy getStrategy(TYPE type) {
PrintStrategy st = null;
switch(type) {
case COMMA:
(3);
break;
case DOTS:
(4);
break;
case LINE:
(5);
break;

}
return st;

}

public static void main(String[] args) {
Interval a = new Interval(1.7,2.1);
a.printInterval(getStrategy(TYPE.COMMA));
a.printInterval(getStrategy(TYPE.DOTS));
a.printInterval(getStrategy(TYPE.LINE));

}

}
答案与解析

平台参考答案(原意保留)

(1)void doPrint(Interval val);

(2)ptr.doPrint(this)

(3)st=new Printintervalscomma()

(4)st=new PrintIntervalsDots()

(5)st=new PrintIntervalsLine()

解析与纠错

填空。 (1)void doPrint(Interval val);(2)ptr.doPrint(this);(3)st = new PrintIntervalsComma();(4)st = new PrintIntervalsDots();(5)st = new PrintIntervalsLine()。原空位之后已经有分号。平台(1)末尾再给一个分号属于冗余,Java接口允许空声明,主要错误不是这个多余分号。

策略委派。 Interval把自身传给PrintStrategy接口的方法,各具体策略读取相同上下界,决定用逗号、省略号还是连字符连接。改变策略即可改变显示格式,无须在Interval内部针对每种输出格式增加分支。getStrategy承担策略对象的创建和选择。

代码纠错。 原页已确认:题干getLower写成retun lowerBound,需改为return lowerBound;平台(3)的Printintervalscomma与已声明的PrintIntervalsComma大小写不一致,会导致类无法解析。可见题干和平台答案保留这些原始拼写,完整参考实现另行给出。

本题Java省略号策略实际输出单个字符…,与C++题的三个句点...不同,不能在原代码中默默统一。图把Interval与策略画为组合,但代码只通过方法参数临时使用策略,没有存储或拥有它;严格的代码对应关系更接近依赖,若要求体现组合应另设计持有和生命周期管理。

Java21验证了原例三种精确输出、负数与零、相等和反向上下界、自定义策略的调用次数与接收对象,以及空策略和空类型的拒绝。题干未定义区间合法性规则,示例仅负责格式化,不擅自交换反向上下界。

补充与完整参考实现

以下为按题意编写并验证的修正示例,包含填空之外的修正与边界处理。

import java.util.Objects;

enum TYPE { COMMA, DOTS, LINE }
interface PrintStrategy {
    void doPrint(Interval val);
}
class Interval {
    private final double lowerBound;
    private final double upperBound;
    public Interval(double lower, double upper) {
        lowerBound = lower;
        upperBound = upper;
    }
    public void printInterval(PrintStrategy ptr) {
        Objects.requireNonNull(ptr, "strategy").doPrint(this);
    }
    public double getLower() { return lowerBound; }
    public double getUpper() { return upperBound; }
}
class PrintIntervalsComma implements PrintStrategy {
    public void doPrint(Interval val) {
        System.out.println("[" + val.getLower() + "," + val.getUpper() + "]");
    }
}
class PrintIntervalsDots implements PrintStrategy {
    public void doPrint(Interval val) {
        System.out.println("[" + val.getLower() + "…" + val.getUpper() + "]");
    }
}
class PrintIntervalsLine implements PrintStrategy {
    public void doPrint(Interval val) {
        System.out.println("[" + val.getLower() + "-" + val.getUpper() + "]");
    }
}
public class Strategy {
    public static PrintStrategy getStrategy(TYPE type) {
        return switch (Objects.requireNonNull(type, "type")) {
            case COMMA -> new PrintIntervalsComma();
            case DOTS -> new PrintIntervalsDots();
            case LINE -> new PrintIntervalsLine();
        };
    }
    public static void main(String[] args) {
        Interval a = new Interval(1.7, 2.1);
        for (TYPE type : TYPE.values()) a.printInterval(getStrategy(type));
    }
}

2022年下半年 · 第 5 题:医院就医流程的Java程序填空

展开原题(先作答,再查看解析)

医院就医流程的Java程序填空

医院患者原本须分别与挂号、门诊、取药等部门交互。采用Facade(外观)模式,设置接待员代患者完成这些步骤,使患者只需面对一个一致的接口。图5-1为该场景的类图。

图5-1 类图

【Java代码】

import java.util.*;  
interface Patient{  
(1);  
}  
interface Disposer{  
(2);  
}  
class Registry implements Disposer{ //挂号  
public void dispose(Patient patient){  
System.out.println("I am registering..."+patient.getName());  
}  
}  
class Doctor implements Disposer{ //医生门诊  
public void dispose(Patient patient){  
System.out.println("I am diagnosing..."+patient.getName());

}

}

class Pharmacy implements Disposer{

public void dispose(Patient patient){

System.out.println("I am giving medicine..."+patient.getName());

}

}

class Facade{

private Patient patient;

public Facade(Patient patient){ this.patient = patient; }

public void dispose(){

Registry registry = new Registry();

Doctor doctor = new Doctor();

Pharmacy ph = new Pharmacy();

registry.dispose(patient);

doctor.dispose(patient);

ph.dispose(patient);

}

}

class ConcretePatient implements Patient{

private String name;

public ConcretePatient(String name){this.name = name;}

public String getName(){ return name;}

}

class FacadeTest{

public static void main(String[] args){

Patient patient = (3);

(4) f = (5);

(6);

}

}

根据说明和Java代码填写(1)~(6)。

答案与解析

平台参考答案(原文保留)

1:String getName()

2:void dispose(Patient patient)

3:new ConcretePatient(“abc”) //这里参数是合法字符串即可

4:Façade

5:new Facade(patient)

6:f.dispose()

解析与纠错

六空的可编译写法为:(1)String getName();(2)void dispose(Patient patient);(3)new ConcretePatient("abc"),字符串可替换为其它合法字符串;(4)Facade;(5)new Facade(patient);(6)f.dispose()。题干各空后已经给出分号,填空时不必重复。

Patient提供姓名查询,Disposer统一各部门的处理操作。Facade保存同一位患者,并依次调用Registry、Doctor、Pharmacy,分别完成挂号、门诊和取药;客户端只需构造Facade再调用dispose,不必直接组织三个部门。

原站可见页面已核实:Registry implements Disposer后确为全角{;原类和构造器均名为Facade,而平台第(4)空确为带ç的Façade。这是不同的Java标识符,应按原类声明使用Facade;全角{须在整理实现中改为半角{。平台答案中的全角方法括号和中文字符串引号同样需换成代码所需的半角符号。原题及平台答案保留原样,不能仅填入平台字符串就声称原段可编译;上述改动仅属于整理修正。

2022年上半年 · 第 6 题:状态保存与恢复(Java)

展开原题(先作答,再查看解析)

状态保存与恢复(Java)

阅读下列说明和Java代码,将应填入(n)处的字句写在答题纸的对应栏内。
【说明】
在软件系统中,通常都会给用户提供取消、不确定或者错误操作的选择,允许将系统恢复到原先的状态。现使用备忘录(Memento)模式实现该要求,得到如图6-1所示的类图。Memento包含了要被恢复的状态。Originator创建并在Memento中存储状态。Caretaker负责从Memento中恢复状态。

图6-1 类图

【Java代码】

import java.util.*;

class Memento {

private String state;

public Memento(String state){this.state=state;}

public String getState(){return state;}

}

class Originator{

private String state;

public void setState(String state){this.state=state;}

public String getState(){ retum state;}

public Memento saveStateToMemento( ){

return (1);

}

public void getStateFromMemento(Memento Memento){

state =(2);

}

}

class CareTaker{

private List < Memento > mementoList= new ArrayList < Memento > ();

public(3){

mementoList.add(state);

}

public (4){

return memensoList.get(index);

}

}

class MementoPaneDems{

public static void main(String[] args) {

Originator originator =new Originator();

CareTaker careTaker=new careTaker();

originator.setState("State #1");

originator.setState("State #2");

careTaker.add( (5) );

originator.setState("State #3");

careTaker.add( (6) );

originator.setState("State #4");

System.out.println("Current State"+originator.getState());

originator.getStateFromMemento(careTaker.get(0));

System.out.println("Frist saved State"+originator.getState());

originator.getStateFromMemento(careTaker.get(1));

System.out.println("Second saved State"+originator.getState());

}

}
答案与解析

平台参考答案(原意保留)

(1) new Memento(state)
(2) Memento.getState()
(3) void add(Memento state)
(4) Memento get(int index)
(5) originator.saveStateToMemento()
(6) originator.saveStateToMemento()

解析与纠错

填空。 (1)new Memento(state);(2)Memento.getState();(3)void add(Memento state);(4)Memento get(int index);(5)(6)均为originator.saveStateToMemento()。Java构造对象需要new;第(3)处前面只有public,故还需void,不能直接照搬C++题的空位内容。

快照保存时点与C++题一致:先保存State #2,再保存State #3,当前值最终为State #4;恢复第0、第1项后分别得到State #2和State #3。String不可变,把当前字符串引用保存到一个独立Memento后,后续给Originator.state重新赋值不会更改之前的快照。

非空号错误。 已回看原页确认,六空答案本身正确,但原代码确含全角标点;retum应为return,memensoList应为mementoList,new careTaker()应按类名写成new CareTaker()。Frist saved State只是输出文案的拼写错误,补充实现也统一为First。只填空而不修正这些非空号错误,程序仍无法编译。

与C++题相同,实际恢复职责在Originator,CareTaker负责保存/取得Memento。补充示例为Memento的状态字段加final以明确快照不可变;初始空串和拒绝null是该示例选定的边界行为,不作为原题隐含条件。Java21验证原例、历史快照稳定性、重复恢复、空串和中文状态,以及越界索引、空状态和空备忘录参数。

补充与完整参考实现

以下是按题意编写并验证的完整修正示例,含填空之外的语法修正与边界处理。

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

final class Memento {
    private final String state;
    public Memento(String state) { this.state=Objects.requireNonNull(state, "state"); }
    public String getState() { return state; }
}
class Originator {
    private String state="";
    public void setState(String state) { this.state=Objects.requireNonNull(state, "state"); }
    public String getState() { return state; }
    public Memento saveStateToMemento() { return new Memento(state); }
    public void getStateFromMemento(Memento memento) {
        state=Objects.requireNonNull(memento, "memento").getState();
    }
}
class CareTaker {
    private final List<Memento> mementoList=new ArrayList<>();
    public void add(Memento state) { mementoList.add(Objects.requireNonNull(state, "memento")); }
    public Memento get(int index) { return mementoList.get(index); }
}
public class MementoPatternDemo {
    public static void main(String[] args) {
        Originator originator=new Originator();
        CareTaker careTaker=new CareTaker();
        originator.setState("State #1");
        originator.setState("State #2");
        careTaker.add(originator.saveStateToMemento());
        originator.setState("State #3");
        careTaker.add(originator.saveStateToMemento());
        originator.setState("State #4");
        System.out.println("Current State"+originator.getState());
        originator.getStateFromMemento(careTaker.get(0));
        System.out.println("First saved State"+originator.getState());
        originator.getStateFromMemento(careTaker.get(1));
        System.out.println("Second saved State"+originator.getState());
    }
}

2021年下半年 · 第 6 题:围棋程序的Java填空

展开原题(先作答,再查看解析)

围棋程序的Java填空

享元(flyweight)模式通过减少创建对象的数量来降低内存占用、提高性能。现开发允许多个玩家联机对弈的网络围棋程序;只有一台服务器,为节省内存,题设采用享元模式实现。图6-1给出类图。

阅读以下Java代码,将空(1)~(5)应填的字句写出。

import java.util.*;

enum PieceColor {BLACK,WHITE} //棋子颜色

class PiecePos { //棋子位置

private int x;

private int y;

public PiecePos(int a,int b){

x=a;

y=b;

}

public int getX( ){

return x;

}

public int getY( ){

return y;

}

}

abstract class Piece { //棋子定义

protected PieceColor m_color; //颜色

protected PiecePos m_pos; //位置

public Piece(PieceColor color,PiecePos pos){

m_color=color;

m_pos=pos;

}

(1);

}

class BlackPiece extends Piece{

public BlackPiece(PieceColor color,PiecePos pos){

super(color,pos);

}

public void draw ( ) {

System.out.println("draw a black piece");

}

}

class WhitePiece extends Piece{

public WhitePiece(PieceColor color,PiecePos pos){

super(color,pos);

}

public void draw( ) {

System.out.println("draw a white piece");

}

}

class PieceBoard{

//棋盘上已有的棋子

private static final ArrayList<(2)> m_arrayPiece=new ArrayList();

private String m_blackName; //黑方名称

private String m_whiteName; //白方名称

public PieceBoard(String black,String white){

m_blackName=black;

m_whiteName=white;

}

//一步棋,在棋盘上放一颗棋子

public void SetePiece(PieceColor color,PiecePos pos){

(3) piece=null;

if(color==PieceColor.BLACK){ //放黑子

piece=new BlackPiece(color,pos); //获取一颗黑子

System.out.println(m_blackName+"在位置("+pos.getX()+","+pos.getY()+")");

(4);

}

else{ //放白子

piece=new WhitePiece(color,pos); //获取一颗白子

System.out.println(m_whiteName+"在位置("+pos.getX()+","+pos.getY()+")");

(5);

}

m_arrayPiece.add(piece);

}

}
答案与解析

平台参考答案(原文保留)

(1)public abstract void draw( )
(2)Piece
(3)Piece
(4)piece.draw( )
(5)piece.draw( )

解析与纠错

(1)为public abstract void draw(),声明无方法体的抽象方法;语句末尾分号已经在空外。(2)、(3)均为Piece,分别作ArrayList的元素类型和局部引用类型。(4)、(5)均为piece.draw(),运行时根据实际对象调用BlackPiece或WhitePiece的实现。

填入平台答案后可以编译,原代码new ArrayList()使用原始类型,会产生未经检查转换的警告;写成new ArrayList<>()可消除该警告,这是整理建议,不改原题。SetePiece虽不是通常的拼法,却是合法方法名,调用时应与声明一致。

原代码每次落子都new一个棋子,不复用同色对象;静态m_arrayPiece仅让所有棋盘对象共享同一个列表,不能使列表内的棋子变成享元。测试建立两个棋盘后,两者访问到同一列表;同色落子的对象身份仍不同。若该字段按注释表示“棋盘上已有的棋子”,多个棋局还会混入同一容器,应把每盘的落子记录与可跨棋盘共享的棋子内部状态分开。题设的享元目标与现有实现存在差距,不能将静态集合等同于已经实现了享元模式。

原页已核实列表声明确为private static final ArrayList<(2)> m_arrayPiece=new ArrayList();。SetePiece的两个分支分别new棋子,代码在m_arrayPiece.add(piece);及方法、类的闭合符号之后结束,没有后续代码。静态列表及缺少对象复用均是完整原题的写法。

2021年上半年 · 第 6 题:层叠菜单的Java填空

展开原题(先作答,再查看解析)

层叠菜单的Java填空

层叠菜单常用于窗口式软件的功能组织。菜单内既可放直接对应功能的菜单项,也可放子菜单;本题用组合(composite)模式实现。图5-1为层叠菜单示例,图5-2为类图。

阅读以下Java代码,填写空(1)~(5)。

import java.util.*;

abstract class MenuComponent { // 构成层叠菜单的元素

(1) String name; // 菜单项或子菜单名称

public void printName() { System.out.println(name); }

public (2);

public abstract boolean removeMenuElement(MenuComponent element);

public (3);

}

class MenuItem extends MenuComponent {

public MenuItem(String name) { this.name=name; }

public boolean addMenuElement(MenuComponent element) { return false; }

public boolean removeMenuElement(MenuComponent element){ return false; }

public List<MenuComponent> getElement(){ return null; }

}

class Menu extends MenuComponent {

private (4);

public Menu(String name){

this.name = name;

this.elementList = new ArrayList<MenuComponent>();

}

public boolean addMenuElement(MenuComponent element){

return elementList.add(element);

}

public boolean removeMenuElement(MenuComponent element){

return elementList.remove(element);

}

public List<MenuComponent> getElement() {return elementList;}

}

class CompositeTest {

public static void main(String[] args) {

MenuComponent mainMenu = new Menu("Insert");

MenuComponent subMenu = new Menu("Chart");

MenuComponent element = new MenuItem("On This Sheet");

(5);

subMenu.addMenuElement(element);

printMenus(mainMenu);

}

private static void printMenus(MenuComponent ifile){

ifile.printName();

List<MenuComponent> children = ifile.getElement();

if(children == null) return; //打印

for(MenuComponent element: children){

printMenus(element);

}

}

}
答案与解析

平台参考答案(原文保留)

(1)protected
(2)abstract boolean addMenuElement(MemuComponent element)
(3)abstract List < MenuComponent > getElement()
(4)ArrayList < MenuComponent > elementList
(5)mainMenu.addMenuElement(subMenu)

解析与纠错

类图中的#name对应(1) protected;(2)应为abstract boolean addMenuElement(MenuComponent element),(3)为abstract List<MenuComponent> getElement()。(4)可按平台写ArrayList<MenuComponent> elementList,其构造器创建ArrayList;声明为接口类型List<MenuComponent>也与现有赋值和方法调用兼容。

(5)为mainMenu.addMenuElement(subMenu)。先把Chart接入Insert,再把On This Sheet接入Chart,printMenus按先当前节点、后子节点的顺序输出三行:Insert、Chart、On This Sheet。

平台第(2)空把参数类型写为MemuComponent,而原类名是MenuComponent;这会导致找不到符号,必须区分原答案与正确拼法。只把该字母错误改正后,给定Java程序可编译,原main输出与预期一致,添加、移除、空子列表和叶子行为测试也通过。叶子返回null、add/remove返回false;组合节点维护子元素列表,由统一接口支持递归遍历。

原页已核实平台第(2)空确写MemuComponent,属于原答案拼写问题。题干在printMenus递归方法及CompositeTest的闭括号之后完整结束,没有遗漏后续代码。

2020年 · 第 5 题:在线支付的Java填空

展开原题(先作答,再查看解析)

在线支付的Java填空

在线支付需要对接不同电子商务平台的接口。希望客户在不同平台购物时,无须关心具体支付接口,因此采用中介者(Mediator)模式整合。图6-1给出类图。

阅读Java代码,填写空(1)~(5)。

import java.util.*;  

interface WebServiceMediator {  
public (1);  
public void SetAmazon(WebService amazon);  
public void SetEbay(WebService ebay);  
}  
abstract class WebService{  
protected (2) mediator;  
public abstract void SetMediator(WebServiceMediator mediator);  
public (3);  
public abstract void search(double money);  
}  
class ConcreteServiceMediator implements WebServiceMediator{  
private WebService amazon;  
private WebService ebay:  
public ConcreteServiceMediator() {  
amazon = null;  
ebay = null;  
}  
public void SetAmazon(WebService amazon) {  
this.amazon-amazon:  
}  
public void SetEbay(WebService ebay){  
this.ebay=ebay;  
}  
public void buy(double money,WebService service){  
if(service==amazon)  
amazon.search(money);  
else  
ebay.search(money);  
}  
}  
class Amazon extends WebService {  
public void SetMediator(WebServiceMediator mediator) {  
this.mediator = mediator:  
}  
public void buyService(double money) {  
(4);  
}  
public void search(double money){  
system.out.println("Amazon receive:"+money);  
}  
}  
class Ebay extends WebService{  
public void SetMediator(WebServiceMediator mediator) {  
this. mediator = mediator;  
}  
public void buyService(double money){  
(5);  
}  
public void search(double money){  
System.out.println("Ebay receive: "+money);  
}

}
答案与解析

平台参考答案(原文保留)

(1) void buy(double money, WebService service)
(2) WebServiceMediator
(3) abstract void buyService(double money)
(4) mediator.buy(money, this)
(5) mediator.buy(money, this)

解析与纠错

(1)为void buy(double money, WebService service),在中介者接口中声明统一购买入口;(2)为WebServiceMediator,使抽象服务持有中介者引用;(3)为abstract void buyService(double money),由具体服务实现。Amazon和Ebay的buyService都把金额与当前对象传给中介者,因此(4)、(5)均为mediator.buy(money, this)。

当前代码的路由是:service等于登记的amazon对象时调用amazon.search,否则调用ebay.search。应按所给代码解释,不改成跨平台互相发送;this用于让中介者识别发起服务。运行前需登记两个服务,并把同一个中介者注入它们,当前题段没有客户端初始化代码。

题面中private WebService ebay:、this.amazon-amazon:、this.mediator = mediator:和system.out.println会导致编译失败,须分别整理为分号、赋值语句及System的正确大小写。仅填五空不能消除这些问题;原页已核实这四处确实如此。Ebay的search方法及类的两个闭括号后即知识点,没有客户端,所给题段完整。上方保留原题代码的写法;补充测试显式创建并连接服务与中介者,客户端属于测试代码。登记为空或传入未登记对象时,原代码没有校验,不能由此认为它已经覆盖真实支付系统的错误处理。

2019年下半年 · 第 6 题:文件管理系统的Java填空

展开原题(先作答,再查看解析)

文件管理系统的Java填空

文件管理系统定义OfficeDoe和DocExplorer。OfficeDoe发生变化时,DocExplorer的所有对象都要更新自身状态;现用观察者(Observer)模式实现,图6-1为类图。

阅读下列Java代码,填写空(1)~(5)。

import java.util.*;  
interface Observer{  
public (1);  
}  

interface Subject{  
public void Attach(Observer obs);  
public void Detach(Observer obs);  
public void Notify();  
public void setStatus(int status);  
public int getStatus();  
}  

class OfficeDoc implements Subject{  
private List < (2) > myObs;  
private String mySubjectName;  
private int m_status;  
public OfficeDoc(String name){  
mySubjectName=name;  
this.myObs=new ArrayList < Observer > ();  
m_status=0;  
}  
public void Attach(Observer obs){this.myObs.add(obs);}  
public void Detach(Observer obs){this.myObs.remove(obs);}  
public void Notify(){  
for(Observer obs:this.myObs){  
(3);  
}  
}  
public void setStatus(int status){  
m_status=status;  
System.out.println("SetStatus subject["+mySubjectName+"]status:"+status);  
}  
public int getStatus(){return m_status;}  
}  

class DocExplorer implements Observer{  
private String myObsName;  
public DocExplorer(String name, (4) sub){  
myObsName=name;  
sub. (5);  
}  
public void update(){  
System.out.println("update observer["+myObsName+"]");  
}  
}  

class ObserverTest{  
public static void main(String []args) {  
System.out.println("Hello World!");  
Subject subjectA=new OfficeDoc("subject A");  
Observer oberverA=new DocExplorer("observer A",subjectA);  
subjectA.setStatus(1);  
subjectA.Notify();  
}  
}
答案与解析

平台参考答案(原文保留)

(1)void update()
(2)Observer
(3)obs.update()
(4)Subject
(5)Attach(this)

解析与纠错

(1)为void update(),是Observer接口的更新操作;(2)为Observer,限定列表元素类型。Notify中的obs是增强for循环的当前元素,因此(3)为obs.update();平台解析把它称作Notify传入的参数并不准确,该方法本身没有参数。

DocExplorer接收抽象目标Subject,所以(4)填Subject;调用sub.Attach(this)把当前对象加入通知列表,所以(5)填Attach(this)。setStatus改变状态并打印消息,随后main显式调用Notify才会通知观察者;DocExplorer.update仅打印通知文字,没有进一步复制目标状态。

五空代入后,示例输出应为:

1
2
3
Hello World!
SetStatus subject[subject A]status:1
update observer[observer A]

原页已核实说明两处均为OfficeDoe,类图与Java代码为OfficeDoc;说明和代码均保留原写法。main局部变量oberverA虽少一个s,但前后没有错误引用,不影响这段程序编译。

2019年上半年 · 第 5 题:竞速游戏的Java填空

展开原题(先作答,再查看解析)

竞速游戏的Java填空

某软件公司开发汽车竞速游戏,需要模拟长轮胎、短轮胎急刹车在路面留下的不同痕迹,并考虑后续扩展更多轮胎。采用策略(Strategy)模式,图5-1给出类图。

阅读Java代码,填写空(1)~(5)。

import java.util.*;  
interface BrakeBehavior {  
public (1);  
/* 其余代码省略 */  
};  
class LongWheelBrake implements BrakeBehavior {  
public void stop() { System.out.println("模拟长轮胎刹车痕迹! "); }  
/* 其余代码省略 */  
};  

class ShortWheelBrake implements BrakeBehavior {  
public void stop() { System.out.println("模拟短轮胎刹车痕迹! "); }  
/* 其余代码省略 */  
};  
abstract class Car {  
protected (2) wheel;  
public void brake() { (3); }  
/* 其余代码省略 */  
}:  
class ShortWheelCar extends Car {  
public ShortWheelCar(BrakeBehavior behavior) {  
(4);  
}  
/* 其余代码省略 */  
};  
class StrategyTest{  
public static void main(String[] args) {  
BrakeBehavior brake = new ShortWheelBrake();  
ShortWheelCar car1 = new ShortWheelCar(brake);  
car1. (5);  
}  
}
答案与解析

平台参考答案(原文保留)

(1)void stop()
(2)BrakeBehavior
(3)wheel.stop()
(4)wheel=behavior
(5)brake()

解析与纠错

BrakeBehavior约定统一刹车操作,(1)为void stop()。Car持有策略引用,所以(2)为BrakeBehavior,(3)用wheel.stop()委托调用。(4)把传入策略保存到继承来的wheel字段,即wheel=behavior;(5)通过车辆的brake()入口执行,示例会输出短轮胎刹车痕迹。

替换传入的BrakeBehavior实现即可改变行为,新增策略也不必修改Car.brake的委托逻辑。构造器中behavior是参数、wheel是被赋值的成员,平台“对参数wheel赋值”的说法不准确。原题没有策略setter,不能称其已经提供运行期间切换接口。

题图在BrakeBehavior下面把具体策略标成LongWheelCar、ShortWheelCar,与代码中的LongWheelBrake、ShortWheelBrake及上方车辆继承层次混淆;学习时应将下面两类理解为具体刹车策略,原图保留。Car结束处的}:会造成Java语法错误,整理版仅将冒号去掉。原页已核实Car确以}:结束,“其余代码省略”均为题面明示,五空与StrategyTest.main完整,末尾两个闭括号后即知识点。保留这些省略标记,不将其判为提取缺失。

2018年下半年 · 第 6 题:航空会员积分的Java填空

展开原题(先作答,再查看解析)

航空会员积分的Java填空

某航空公司会员积分系统分普卡Basic、银卡Silver、金卡Gold三个等级;非会员Non Member可申请成为普卡。等级按一年内累积的里程数调整。图6-1给出调整状态图,图6-2是采用状态State模式设计的类图。

阅读Java代码,填写空(1)~(5)。

import java.util.*;  

abstract class CState {  
public int flyMiles; // 里程数  
public (1) ; // 根据累积里程数调整会员等级  
}  
class CNoCustomer extends CState { // 非会员  
public double travel(int miles, FrequentFlyer context) {  
System.out.println ( “Your travel will not account for points”);  
return miles; // 不累积里程数  
}  
}  
class CBasic extends CState { // 普卡会员  
public double travel(int miles, FrequentFlyer context) {  
if(context.flyMiles >= 25000 && context.flyMiles < 50000)  
(2) ;  
if(context.flyMiles >= 50000)  
(3) ;  
return miles;  
}  
}  
class CGold extends CState { // 金卡会员  
public double travel(int miles, FrequentFlyer context) {  
if(context.flyMiles >= 25000 && context.flyMiles < 50000)  
(4) ;  
if(context.flyMiles < 25000)  
(5) ;  
return miles + 0.5*miles; // 累积里程数  
}  
}  
class CSilver extends CState { // 银卡会员  
public double travel(int miles, FrequentFlyer context) {  
if(context.flyMiles <= 25000)  
context.setState(new Cbasic());  
if(context.flyMiles >= 50000)  
context.setState(new Cgold());  
return (miles + 0.25*miles); // 累积里程数  
}  
}  
class FrequentFlyer {  
CState state;  
double flyMiles;  
public FrequentFlyer(){  
state = new CnoCustomer();  
flyMiles = 0;  
setState(state);  
}  
public void setState(CState state){ this.state = state; }  
public void travel(int miles) {  
double bonusMiles = state.travel(miles,this);  
flyMiles = flyMiles + bonusMiles;  
}  
}
答案与解析

平台参考答案(原文保留)

(1)abstract double travel(int miles,FrequentFlyer context)

(2)context.setState(new CSilver())

(3)context.setState(new CGold ())

(4)context.setState(new CSilver())

(5)context.setState(new CBasic())

解析与纠错

(1)补abstract double travel(int miles,FrequentFlyer context)。按状态图,(2)创建CSilver,(3)创建CGold,(4)创建CSilver,(5)创建CBasic,并分别调用context.setState。平台解析把银卡上限写成5000,正确上限为50000。

银卡代码却在flyMiles<=25000时降普卡,和图中严格小于25000不一致。恰为25000时,从银卡调用travel(0)会变普卡,再调用一次又变银卡;若按图整理,这一处应为<25000。原页已核实这里确含等号,题干保留原写法。

CNoCustomer、CBasic、CGold的声明与new CnoCustomer()/Cbasic()/Cgold()不一致,Java区分大小写;弯引号、全角分号也需明确修正后才能编译。非会员方法返回miles,上下文照样累加,与“不累积里程数”注释冲突。普卡1倍、银卡1.25倍、金卡1.5倍是本段代码行为,不可按另一语言题改动原文。

本次里程在状态方法返回后才累加,故按飞行前总里程判断会使跨阈值的升级滞后;题面未提供注册为普卡、维护一年内累计值等完整业务流程。测试分别保留这些行为来核对问题,不用静默修正来冒充原程序正确。原页已核实类名大小写、弯引号、全角分号、非会员与普卡返回miles均如此。travel累加flyMiles后两个闭括号直接结束,没有额外代码;来源完整。

2018年上半年 · 第 6 题:产品构建的Java填空

展开原题(先作答,再查看解析)

产品构建的Java填空

生成器(Builder)模式将复杂对象的构建过程与表示分离,使同一构建过程可以创建不同表示。图6-1给出其类图。

阅读下列Java代码,填写空(1)~(5)。

import java.util.*;

class Product {

private String partA;

private String partB;

public Product() {}

public void setPartA(String s) { partA = s; }

public void setPartB(String s) { partB = s; }

}

interface Builder {

public

(1)

;

public void buildPartB();

public

(2)

;

}

class ConcreteBuilder1 implements Builder {

private Product product;

public ConcreteBuilder1() { product = new Product(); }

public void buildPartA() {

(3)

("Component A"); }

public void buildPartB() {

(4)

("Component B"); }

public Product getResult() { return product;}

}

class ConcreteBuilder2 implements Builder {

// 代码省略

}

class Director {

private Builder builder;

public Director(Builder builder) {this.builder = builder; }

public void construct() {

(5)

;

// 代码省略

}

}

class Test {

public static void main(String[] args) {

Director director1 = new Director(new ConcreteBuilder1());

director1.construct();

}

}
答案与解析

平台参考答案(原文保留)

(1)void buildPartA()

(2)Product getResult()

(3)product.setPartA

(4)product.setPartB

(5)builder.buildPartA()或者 builder.buildPartB()

解析与纠错

(1)补void buildPartA(),(2)补Product getResult();Java接口中的这两条方法声明可互换顺序。(3)/(4)分别为product.setPartA和product.setPartB。Director持有Builder接口,具体构建器负责配置并返回Product,这样相同的构建步骤可以配合不同具体构建器。

(5)后有明示“代码省略”,平台接受builder.buildPartA()或builder.buildPartB()。现有可见片段只能证明调用了所填的那一步,不能据此说A和B一定都已设置;补充测试分别验证两种允许答案,再用明确补全的步骤序列验证完整产品。

原题的ConcreteBuilder2是非abstract类,却只保留省略注释,没有实现接口方法,照抄全部片段会编译失败。这是明示省略与可编译性问题,不能据此认定提取缺行;原页已核实Product、Builder、ConcreteBuilder½、Director、Test齐全,ConcreteBuilder2确为非abstract且仅省略注释;main中director1.construct();及两个闭括号后直接知识点,来源完整。测试版明确将该占位类标为abstract,因题中实际创建的是ConcreteBuilder1,不更改其构建行为。全部补全与测试方法仅用于验证,不冒充原题源码。

2017年下半年 · 第 6 题:跨平台图像预览的Java填空

展开原题(先作答,再查看解析)

跨平台图像预览的Java填空

图像预览程序需查看BMP、JPEG、GIF三种格式,并能运行于Windows和Linux。为方便扩展新的文件格式、操作系统并减少所需子类数量,采用桥接(Bridge)模式,类图如图6-1。

阅读Java代码,填写空(1)~(5)。

import java.util.*;

class Matrix{ // 各种格式的文件最终都被转化为像素矩阵

// 此处代码省略

};

abstract class Implementor{

public (1); // 显示像素矩阵 m

};

class WinImp extends Implementor{

public void doPaint(Matrix m){ // 调用 Windows 系统的绘制函数绘制像素矩阵

}

};

class LinuxImp extends Implementor{

public void doPaint(Matrix m){ // 调用 Linux 系统的绘制函数绘制像素矩阵

}

};

abstract class Image {

public void setImp(Implementor imp) { this.imp = imp; }

public abstract void parseFile(String fileName);

protected Implementor imp;

};

class BMPImage extends Image{

// 此处代码省略

};

class GIFImage extends Image{

public void parseFile(String fileName) {

// 此处解析 BMP 文件并获得一个像素矩阵对象 m

(2); // 显示像素矩阵 m

}

};

class JPEGImage extends Image{

//此处代码省略

};

class Main{

public static void main(String[]args) {

// 在 Linux 操作系统上查看 demo.gif 图像文件

Image image= (3);

Implementor imageImp= (4);

(5);

image.parseFile("demo.gif");

}

}
答案与解析

平台参考答案(原文保留)

1.abstract void doPaint(Matrix m)
2.imp.doPaint(m)
3.new GIFImage()
4.new LinuxImp()
5.image.setImp(imageImp)

解析与纠错

(1)在已有public后补abstract void doPaint(Matrix m),(2)为imp.doPaint(m);(3)/(4)分别new GIFImage()、new LinuxImp(),(5)为image.setImp(imageImp)。这与C++题同属桥接意图,但Java抽象方法和引用调用的原空不同,保留为独立题。

GIFImage的注释却写“解析BMP文件”,与类名及demo.gif不一致;所示代码没有声明矩阵m,也没有真实解析过程。BMPImage、JPEGImage不是abstract,却明示省略了parseFile实现,JPEGImage尾还用全角分号;照抄不能直接编译。原页已核实BMP注释、未声明m、两个非abstract占位类及全角分号均如此;main以parseFile调用和两个闭括号结束后即知识点。来源完整,显式省略不作为提取缺失。

补充测试明确将未用的占位子类标为abstract,整理全角分号,并在GIFImage中提供测试矩阵;这些属于测试补全。验证的是图像对象向当前绘制实现传递矩阵,支持换绘制实现及新增格式,不包括文件解码算法或操作系统图形接口。

2017年上半年 · 第 6 题:套餐制作的Java填空

展开原题(先作答,再查看解析)

套餐制作的Java填空

快餐厅制作儿童套餐,通常包括主餐(各类比萨)、饮料、玩具。餐品种类可不同,制作过程相同,由前台服务员Waiter调度厨师。采用生成器(Builder)模式实现制作过程,类图见图6-1。

阅读Java程序,填写空(1)~(5)。

class Pizza {

private String parts;

public void setParts(String parts) { this.parts = parts; }

public String toString() { return this.parts; }

}

abstract class PizzaBuilder {

protected Pizza pizza;

public Pizza getPizza() { return pizza; }

public void createNewPizza() { pizza = new Pizza(); }

public (1);

}

class HawaiianPizzaBuilder extends PizzaBuilder {

public void buildParts() { pizza.setParts("cross + mild + ham&pineapp1e”};

}

class SpicyPizzaBuilder extends PizzaBuilder {

public void buildParts() { pizza.setParts("pan baked + hot + pepperoni&salami"); }

}

class Waiter {

private PizzaBuilder pizzaBuilder;

public void setPizzaBuilder(PizzaBuilder pizzaBuilder) { /*设置构建器*/

( 2 );

}

public Pizza getPizza(){ return pizzaBuilder.getPizza(); }

public void construct() { /*构建*/

pizzaBuilder.createNewPizza();

( 3 );

}

}

Class FastFoodOrdering {

public static viod mainSting[]args) {

Waiter waiter = new Waiter();

PizzaBuilder hawaiian_pizzabuilder = new HawaiianPizzaBuilder();

( 4 );

( 5 );

System.out.println("pizza: " + waiter.getPizza());

}

}

题目给出的输出为:

Pizza:cross + mild + ham&pineapple
答案与解析

平台参考答案(原文保留)

(1)abstract void buildParts();

(2)this.pizzaBuilder=pizzaBuilder

(3)pizzaBuilder.buildParts()

(4)waiter.setPizzaBuilder(hawaiian_pizzabuilder)

(5)waiter.construct()

解析与纠错

Java的PizzaBuilder是抽象类,(1)补抽象buildParts声明;(2)保存生成器引用,(3)在createNewPizza后调用buildParts,(4)设置夏威夷生成器,(5)构建。Pizza重写toString,因此字符串拼接会显示parts;这与C++显式调用getParts的原空不同,保留独立题。

原字段后全角分号、Class、viod和mainSting[]args)不符合Java语法;夏威夷部件语句含弯引号,方法调用与括号也残损。整理版需明确修成标准main(String[] args)及完整的setParts调用,原题代码仍原样保留。平台(1)答案自带分号,而题面空后另有分号;Java类体允许额外空声明,但学习代码只需一个终止分号。

字符串末尾原写pineapp1e(数字1),题给输出却为pineapple(字母l);代码打印前缀为pizza:,题给输出是Pizza:,大小写和空格也不一致。只修语法的测试保留这些字面内容,不能声称会输出题后那一行。类图夏威夷方法builderParts与代码buildParts也不一致,测试按基类接口统一。两种语言的Spicy部件原文也不同,不能拿另一题的字符串替换本题。

测试验证生成流程、切换两种生成器、产品重新创建和toString输出。只覆盖题中Pizza构建,不臆补饮料、玩具或未说明的套餐逻辑。

原站可见页面已核实上述全角分号、数字1、弯引号、缺右括号、main签名、两处分号及输出标签大小写均为原题写法。末尾两个闭括号后直接给出程序输出说明,没有其他代码;来源完整,语法整理不作为原题原文。

2016年下半年 · 第 6 题:发票打印的Java填空

展开原题(先作答,再查看解析)

发票打印的Java填空

发票Invoice由抬头Head、正文和脚注Foot构成。使用装饰(Decorator)模式实现打印功能,类图见图6-1。

阅读Java代码,填写带下划线的空(1)、(2)、(3)、(4)、(5)。

class invoice{

public void printInvoice(){

System.out.println ( "This is the content of the invoice!");

}

}

class Decorator extends Invoice {

protected Invoice ticket;

public Decorator(Invoice t){

ticket = t;

}

public void printInvoice(){

if(ticket!= null)

(1

)

;

}

}

class HeadDecorator extends Decorator{

public HeadDecorator(Invoice t){

super(t);

}

public void printInvoice (){

Systent.out.println( "This is the header of the invoice! ");

(2

)

;

}

}

class FootDecorator extends Decorator {

public FootDecorator(Invoice t){

super(t);

}

public void printInvoice(){

( 3

)

;

Systent.out.println( "This is the footnote of the invoice! ");

}

}

Class test {

public static void main()(String[] args){

Invoice t =new Invioce();

Invoice ticket;

ticket=

(4

)

;

ticket.printInvoice();

Systent.out.println(“------------------“);

ticket=

(5

)

;

ticket.printInvoice();

}

}

题目给出的输出为:

This is the header of the invoice!

This is the content of the invoice!

This is the footnote of the invoice!

----------------------------

This is the header of the invoice!

This is the footnote of the invoice!
答案与解析

平台参考答案(原文保留)

(1) ticket.printInvoice()

(2) super.printInvoice()

(3) super.printInvoice()

(4) new HeadDecorator(new FootDecorator(t))

(5) new HeadDecorator(new FootDecorator(null))

解析与纠错

(1)对被包装的ticket调用printInvoice;(2)、(3)调用super.printInvoice,复用Decorator的空检查与委托。第(4)空将已有t先包脚注、再包抬头;第(5)空以null作最内层组件,仍保留脚注及抬头,所以不打印正文。Java动态派发不需要C++的virtual关键字,两个语言的原空与装配目标分别保留。

原代码class invoice与所用Invoice大小写不同,new Invioce拼错,Systent、Class以及main()(String[] args)也不是合法写法;分隔线使用弯引号。整理版仅修这些语法,保留原题代码在上方,不把补正当作原文。

代码的分隔线有18个“-”,题后给定输出有28个;抬头、脚注代码字符串在感叹号后还有一个空格,题给输出没有。因此仅修编译错误仍不能与题后输出逐字符相同,正文保留两者差异;测试分别核对调用顺序及代码实际字面输出,不暗改字符串。

平台解析把super调用的理由归于“借助超类构造函数”,这个表述不精确:构造函数设置包装对象,super.printInvoice则显式调用超类的普通方法。直接再次调用自身printInvoice会递归;改为调用Invoice的基本正文也会绕过包装链及null分支。

原站可见页面截图已核实上述大小写、拼写、main签名、弯引号、字符串尾空格及分隔线长度差异。五个待填位置均带下划线;末尾函数与类的两个闭括号后直接是程序输出说明,没有额外类或代码,来源完整。

2016年上半年 · 第 6 题:Dutch地址接口的Java填空

展开原题(先作答,再查看解析)

Dutch地址接口的Java填空

现有Address类已实现地址信息显示,现需提供Dutch语言接口,并考虑以后增加其他语言接口。采用适配器(Adapter)模式,类图如图6-1。

阅读Java代码,填写空(1)~(5)。

import java.util.*;

Class Address{

public void street() { //实现代码省略 }

public void zip() { //实现代码省略 }

public void city() { //实现代码省略 }

//其他成员省略

};

class DutchAddress{

public void straat() { //实现代码省略 }

public void postcode() { //实现代码省略 }

public void plaats() { //实现代码省略 }

//其他成员省略

};

class DutchAddressAdapter extends DutchAddress {

private (1);

public DutchAddressAdapter (Address addr){

address= addr;

}

public void straat() {

(2);

}

public void postcode() {

(3);

}

public void plaats(){

(4);

}

//其他成员省略

};

class Test {

public static void main(String[] args) {

Address addr= new Address();

(5);

System.out.println("\n The DutchAddress\n");

testDutch(addrAdapter);

}

Static void testDutch(DutchAddress addr){

addr.straat();

addr.postcode();

addr.plaats();

}

}
答案与解析

平台参考答案(原文保留)

(1)Address address;

(2)address.street();

(3)address.zip();

(4)address.city();

(5)DutchAddress addrAdapter=new DutchAddressAdapter(addr);

解析与纠错

Java中适配器用成员Address address保存被适配对象,构造函数接收并记录它;straat、postcode、plaats分别调用address.street()、address.zip()、address.city()。最后创建DutchAddressAdapter赋给DutchAddress引用,testDutch通过多态调用适配器三个方法。成员类型仍是Address,不能改成目标接口DutchAddress而导致委托方向错误。

原文本的Class、Static首字母大写不合法;Address和DutchAddress的六个占位方法把闭合右括号写在//行注释后,按现有换行直接编译时右括号会被注释掉。明确整理版将关键字改小写,并把“实现代码省略”改为块注释,让右括号生效;没有臆造被省略的地址显示实现。五处原空和平台答案均带末尾分号,整理填空只保留一份分号,平台原答案不改。

使用空占位体可验证整理后的完整程序编译,运行只显示标题。独立行为测试以记录调用的Address子类替代省略行为,验证目标接口的动态派发、三个方法对应、多个适配器各自保存正确对象及重复调用;不据此声称地址本地化功能已经实现。

原站可见页面已核实Class、Static的大小写及六个行注释后同一行的右括号;main之后是testDutch,最后两个右括号后无额外成员。五个题面空后和五项参考答案末尾均有分号,原题和原答案各自保留。

2015年上半年 · 第 6 题:馆藏页数统计(Java)

展开原题(先作答,再查看解析)

馆藏页数统计(Java)

图书管理系统保存图书和论文两类文献,需要统计全部馆藏文献的总页码。例如,一本540页的图书加两篇各25页的论文,共590页。按Visitor(访问者)模式设计,类图如下。

图6-1 Visitor模式类图。

根据说明与下列Java代码填写(1)~(5)。

import java.util.*;

interface LibraryVisitor {

(1);

(2);

void printSum();

}

class LibrarySumPrintVisitor implements LibraryVisitor { //打印总页数

private int sum = 0;

public void visit(Book p_book) {

sum = sum + p_book.getNumberOfPages();

}

public void visit(Article p_article) {

sum = sum + p_article.getNumberOfPages();

}

public void printSum(){

System.out.println("SUM = " + sum);

}

}

interface LibraryItemInterface {

(3);

}

class Article implements LibraryItemInterface{

private String m_title; //论文名

private String m_author; //论文作者

private int m_start_page;

private int m_end_page;

public Article(String p_author, String p_title,int p_start_page,int p_end_page){

m_title=p_title;

m_author= p_author;

m_end_page=p_end_page;

}

public int getNumberOfPages(){

return m_end_page - m_start_page;

}

public void accept(LibraryVisitor visitor){

(4);

}

}

class Book implements LibraryItemInterface{

private String m_title; //书名

private String m_author; //书作者

private int m_pages; //页教

public Book(String p_author, String p_title,int p_ pages){

m_title= p_title;

m_author= p_author;

m_pages= p_pages;

}

public int getNumberOfPages(){

return m_pages;

}

public void accept(LibraryVisitor visitor){

(5);

}

}
答案与解析

平台参考答案(原文保留)

(1)void visit(Book p_book)

(2)void visit(Article p_article)

(3)void accept(LibraryVisitor visitor)

(4)visitor.visit(this)

(5)visitor.visit(this)

解析与纠错

LibraryVisitor声明void visit(Book p_book)和void visit(Article p_article),分别填第(1)、(2)空。LibraryItemInterface声明void accept(LibraryVisitor visitor),填第(3)空。Article、Book的accept调用visitor.visit(this),填第(4)、(5)空。

即使集合元素的引用类型为LibraryItemInterface,运行时仍会进入具体元素的accept;其中this分别是Article或Book,选择对应visit重载。sum属于一次访问者实例,遍历集合时累计;重新用同一实例遍历会继续累加,开始另一轮独立统计应新建访问者或显式重置。

补充说明

原代码问题与行为纠错:Book构造参数中出现“p_ pages”,直接编译会失败。Article构造函数没有给m_start_page赋值;Java实例整数字段会默认为0,因而实际返回end-0,并不等于用传入起始页计算的长度。应显式加入m_start_page=p_start_page。

页数的区间约定仍须区分:半开区间[start,end)用end-start,首尾页都计入用end-start+1。用半开区间[10,35)、[40,65)构造两篇25页论文,原构造函数会算成35与65,加540页图书得到640;补上起始页赋值后才得到590。若采用含首尾的第10~34、第40~64页,则应同时使用加1的公式。

下方给出明确采用含首尾页码、修正参数拼写和起始页赋值的完整类定义。它是整理示例,不替换上方原题代码;使用时还需由调用方建立集合、创建访问者并调用accept。原解析偶尔写成大写Visitor.visit(this),应使用参数变量visitor。

import java.util.*;

interface LibraryVisitor {

void visit(Book p_book);

void visit(Article p_article);

void printSum();

}

class LibrarySumPrintVisitor implements LibraryVisitor { //打印总页数

private int sum = 0;

public void visit(Book p_book) {

sum = sum + p_book.getNumberOfPages();

}

public void visit(Article p_article) {

sum = sum + p_article.getNumberOfPages();

}

public void printSum(){

System.out.println("SUM = " + sum);

}

}

interface LibraryItemInterface {

void accept(LibraryVisitor visitor);

}

class Article implements LibraryItemInterface{

private String m_title; //论文名

private String m_author; //论文作者

private int m_start_page;

private int m_end_page;

public Article(String p_author, String p_title,int p_start_page,int p_end_page){

m_title=p_title;

m_author= p_author;

m_start_page=p_start_page;
m_end_page=p_end_page;

}

public int getNumberOfPages(){

return m_end_page - m_start_page + 1;

}

public void accept(LibraryVisitor visitor){

visitor.visit(this);

}

}

class Book implements LibraryItemInterface{

private String m_title; //书名

private String m_author; //书作者

private int m_pages; //页教

public Book(String p_author, String p_title,int p_pages){

m_title= p_title;

m_author= p_author;

m_pages= p_pages;

}

public int getNumberOfPages(){

return m_pages;

}

public void accept(LibraryVisitor visitor){

visitor.visit(this);

}

}

课件来源

整理自《2026 版本软件设计师考试精讲班视频教程讲义电子版》“【软件设计】面向对象程序设计”整章,通过 Obsidian Web Clipper 提取,于 2026-09-28 核对。六张原图均已本地化;表格转录、语法纠错、推导与可运行版本为笔记补充。

课件 来源 ID 内容
课程内容提要 161770 语法、填空技巧与设计模式实战的学习范围
C++语法要点 161772 类、继承、类外定义、虚函数、指针与引用
Java语法要点 161773 类与接口、抽象、继承及语法填空
设计模式技巧总结 161774 12 条填空提示与易错边界
实战演练 161775 观察者、桥接两个案例,共 12 个填空

评论