1. Purpose
In this portion of the project, you will be building a visual view of your game, and also start building the computer strategies for playing the game. This assignment has several parts. Look over the outline on the left, and skim through the whole assignment, before diving deeply into any one section. The assignment lists requirements in the recommended order you should do them, but future tasks might give you ideas for how to improve earlier ones.
你好,我是悦创。
上一节课,我们通过一个企业内部小助手的案例,学习了 6B 的微调过程。其中,除了微调,还有一种模式就是知识库,用来增强大模型信息检索的能力,我们称之为检索增强生成(RAG),这是目前非常流行的一种做法,知识库模式相比于微调有 2 个好处。
公众号:AI悦创【二维码】

你好,我是悦创。
上节课我们学习了如何构造提示,让 AI 大模型高效输出我们需要的内容,相信你已经迫不及待地体验过了,但是你有没有发现 AI 大模型在使用的过程中是有一些局限的,比如:
- 数据的及时性:大部分 AI 大模型都是预训练的,拿 ChatGPT 举例,3.5 引擎数据更新时间截止到 2022 年 1 月份,4.0 引擎数据更新时间截止到 2023 年 12 月份,也就是说如果我们问一些最新的信息,大模型是不知道的。
- 复杂任务处理:虽然 AI 大模型在问答方面表现出色,但它们并不总是能够处理复杂的任务,比如直接编辑或优化 Word 文档或 PDF 文件。这些任务通常需要特定的软件工具和用户界面,而大模型主要是基于文本的交互(多模态除外)。
- 代码生成与下载:我们希望大模型根据需求描述生成对应的代码,并提供下载链接,大模型也是不支持的。
- 与企业应用场景的集成:在和企业应用场景打通的时候,我们希望大模型读取关系型数据库里的数据,并根据提示进行任务处理,同样大模型也是不支持的。
你好,我是悦创。
这节课我们好好讲讲提示工程,也就是我们常说的 Prompt Engineering。这一节课的内容非常关键,可以这么说,能否充分使用好 AI 大模型,提示是关键,所以现在已经有一种新的职业诞生,就是提示工程师。
目前国内已经有不少公司在招聘提示工程师,薪水接近软件开发工程师的水平,达到 20 万~30 万人民币,国外有的高级提示工程师,薪水已经超越软件开发工程师水平,达到了惊人的 30 万美金以上,要知道 Google 软件开发工程师的平均年薪也就 20 万美金左右。为什么会出现这样的现象?到底什么是提示工程?想了解这个问题,我们先来聊聊什么是提示。
你好,我是悦创。
2022 年 11 月底,OpenAI 发布了 ChatGPT,2023 年 1 月注册用户超过一亿,成为历史上增长最快的应用,上一个纪录保持者是 TikTok,注册用户超过一亿用时 9 个月。2023 年 3 月开始,ChatGPT 燃爆中国互联网界。
实际上国内外同一时期搞大模型的团队很多,为什么 ChatGPT 会突然火起来?还有在 ChatGPT 发布后,为什么各个大厂在短时间内相继发布大模型产品?比如 3 月百度发布文心一言,4 月阿里云发布通义千问,5 月科大讯飞发布星火认知大模型等等。
你好,我是悦创。
欢迎你加入这个专栏,和我来一场 AI 大模型深度游。
近年来,我开始深入研究 AI 领域,阅读了数百篇相关论文,逐渐领略到这一领域的魅力与潜力。对于我们这些具备开发基础的技术人员来说,AI 无疑是一个充满机遇的新领域。近期的两会上,人工智能也再次成为国家战略焦点,预示着未来十年到十五年的行业发展红利。从互联网 + 到人工智能 +,我们将迎来新一轮技术革新和人才需求的增长。
我相信,在未来的发展中,AI 将引领我们走向更加智能、高效的新时代,所以它非常值得我们花时间好好去钻研。
Overview
The topic of the project is the forest fire model, a type of cellular automaton which is introduced in the ‘Forest Fire’ video, found on Blackboard under week 19. The rules are briefly explained below, but you should watch the video for a more detailed explanation.
The deadline for submission is 12:30pm, on Wednesday of week 23. You should submit:
1. Purpose
In this project, you will be building a two-player game with a graphical interface. The design and implementation of this game is left open-ended, for you to figure out, explain, and justify. There will be several components to this project, so do not assume that everything in your code must neatly be described as either “model”, “view”, or “controller”.
In the last lecture we introduced the idea of a “Features
interface” that described a way to
- isolate the controller from the low-level Swing components inside the view, and
- simultaneously, allow the view to have multiple UI components trigger the same logical callback on the controller.
1. 获取代码
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
你好,我是悦创。
不过经常使用 AI 的同学可能会发现,即便 DeepSeek 这样大模型里的佼佼者,也不是万能的。有很多内容其实它是回答不了的。特别是一些垂直领域的知识或实时性比较高的知识。比如我们想做一个天气问答助手,直接调用大模型是不行的:
这时我们就需要想办法让大模型具备与外界进行交互的能力。比如大模型要是能调用高德天气的 API 获取实时天气,就可以解决问题。基于此,行业龙头 OpenAI 为我们提供了解决方案,那就是 Function Calling 机制。该机制可以让大模型审时度势地调用由人类提供的外部工具,从而解决上述问题。
1. 选择题
-
15 >= 8 的运算结果是什么?( B)
- A. False
- B. True
- C. 15
- D. 8
-
下面 print 语句,哪一个是正确的用法?( )
- A. print 龙腾虎跃
- B. print(龙腾虎跃)
- C. print'龙腾虎跃'
- D. print('龙腾虎跃')
-
int(100.5) 的结果是?( )
Project 1
Project brief
musicOnline.com are a recently created business start-up. The idea is that users can register and search for vinyl music (albums, EPs, singles, etc) that other users are selling or they can upload information on a vinyl that they have for sale. If the user is a retailer they will be required to pay for registration. The website will eventually display relevant advertisements but this is not required at the moment. Placeholder images can be used for this.
原文
## 1. Introduction to Views
So far we’ve worked on designing *models* to represent the data relevant to a problem domain, in a form that encapsulates the data behind an interface that clients can use without having to know any implementation details. The model is responsible for ensuring that it can’t get stuck in a bogus or invalid state, and exposes whatever appropriate observations and operations are needed while still preserving this integrity constraint.
We’ve also worked on simple synchronous *controllers* that allow users to interact with a model, in a form that encapsulates the user interactions and can provide feedback to users without having to redundantly ensure any integrity constraints. Moreover, controllers can be customized or enhanced without needing to change the model, making the model more convenient to use without making it any more complex.
Now, it’s time to introduce the third part of the MVC trio: *views*. Views are renderings of the data in the model, and can be as simple as printing debug output to a console, as complex as fancy graphical user interfaces, or anything in between. Dealing with GUIs also brings additional challenges, and we will discuss some of them.
### 1.1 Outline
This lecture starts by adding a text-based, interactive view to the program. This is closest to our earlier design for the controller, but it carves out a view cleanly. This example also illustrates the typical interactions between the model, view and controller in a simple manner.
We then progressively transition to graphical user interfaces. We start with *poorly-designed but working* code, and improve it in three stages. Initially, the code for the view will directly manipulate the model. Our first incremental improvement will decouple the view from the model so that it need not — and in fact *cannot* — do so. Our second improvement will add a new feature to the view, and add the ability to control that in the controller. Finally, our third improvement will generalize the controller to make its UI triggers more customizable.
The code for this lecture is available at the top of this page, as the [MVC code](https://course.ccs.neu.edu/cs3500/lec/gui_basics/code.zip) link. The second link provides code for a program with an incomplete GUI. It is recommended that you complete this exercise to practice with GUIs. Finally the third link provides a solution for this GUI.
## 2. Design
All examples in this lecture follow a common design. The model state (interface `IModel` and implementation `Model`) consists of a single `String` and offers methods to get and update it. All views (coupled with controllers wherever applicable) expose functionality to interactively show this string, and to update it.
### 3` `Text-based UI
In the TextUI directory we show a text-based user interface. The IView interface encapsulates all the methods that a view’s client would need to call: note how they correspond roughly to the “things” that a controller would need to tell the view to do. The TextView class implements this interface. It transmits all messages to a PrintStream object provided to it through its constructor. Similar to the design in [Lecture 8](https://course.ccs.neu.edu/cs3500/lec_controllers_mocks_notes.html) this allows us to test the view using any suitable PrintStream object.
The IController interface represents a controller: it has only method which is called to give control of the application to the controller. The TextController class implements the controller. It works with IModel, IView and Scanner objects to handle model, view and data input functionality respectively. At its heart, the controller goes through a loop that provides users with some options, takes further inputs depending on the chosen options and delegates to the model and view accordingly. Since the sequence of operations is largely fixed, this qualifies as a *synchronous* controller.
## 4. Stage 1: Introduction to GUIs
In the BadDesignButFunctional directory, we have a poorly written implementation of a simple program that uses the same model, but a graphical user interface.
Our `main()` method constructs a `Model`, and constructs a view that is given a reference to the model. The view interface (`IView`) has only three methods: to obtain the text the user has typed in to the text box, to clear that text box, and to echo a string to the label in the UI. The implementation of the view, `JFrameView`, is markedly more complicated in appearance than expected, but it breaks down into several simpler parts.
### 4.1 Frames and controls
Our program uses the Swing framework to show its UI. In Swing, an individual window is known as a *frame*, which can contain controls known as *components*. To create our own window, we design a class that subclasses from `JFrame`, do some work to establish the components in it, and then call `setVisible` to display the window. Within the constructor of our frame, we create four components: a *label* to show some text, a *text box* to edit text, and two *buttons*. Adding several controls to a frame requires that we give them a *layout*, which describes the spatial relationships between the controls. In this example, we’re using a `FlowLayout`, which allows the controls to wrap around as we resize the window. (Try running the project and resizing the window to be narrower than the controls are.) Different layout managers allow adding controls in [different ways](https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html). Once controls are added to the UI, the `pack()` method is used tell the layout manager to determine the actual positions and sizes of all the controls.
In order for the controls to do anything, however, we need to add an *event handler* to them. An event handler, or *callback*, is simply a function that gets called when something interesting occurs. In our case, the clicking of different buttons should trigger a callback. In the jargon of Swing, clicking on buttons triggers their *action*, and so we must supply a function object that implements the [`ActionListener`](https://docs.oracle.com/javase/8/docs/api/java/awt/event/ActionListener.html) interface. (Other controls have additional events besides “actions”.) For convenience, Swing allows us to label each button with a so-called *action command*, which is a `String` of our choosing: when the `ActionListener`’s callback is invoked, it will be given an [`ActionEvent`](https://docs.oracle.com/javase/8/docs/api/java/awt/event/ActionEvent.html) object that knows the action command of the button that was clicked. In this way, we can use a single listener to listen to multiple buttons at once, and distinguish them by means of this command string. See the calls to `setActionCommand` and `setActionListener` and the implementation of `actionPerformed` in JFrameView.java for an example:
\```java
class JFrameView extends JFrame implements ActionListener {
public JFrameView(String caption, IModel model) {
...
echoButton = new JButton("Echo"); // Create a button,
echoButton.setActionCommand("Echo Button"); // set its command,
echoButton.addActionListener(this); // set the callback,
this.add(echoButton); // and add it to the UI
exitButton = new JButton("Exit"); // ditto, for another button
exitButton.setActionCommand("Exit Button");
exitButton.addActionListener(this);
this.add(exitButton);
...
}
@Override
public void actionPerformed(ActionEvent e) {
switch (e.getActionCommand()) {
case "Echo Button": ...
case "Exit Button": ...
}
}
...
}
\```
**Note:** Combining the event handlers of multiple buttons into a single function is only temporarily convenient: often, the code we want to run for one button is completely different from the code we want to run for a different button, so there’s not much benefit from merging them all. Instead, it is more common to create anonymous objects, or (even terser) lambda expressions, so that each button gets its own custom handler. We’ll see other idioms of setting up listeners below.
## 5. Stage 2: Decoupling the view from the model
The code above technically works, but it is very poorly designed: the view is responsible for mutating the model, which means there’s no separation of concerns between this view and any controller, and if we wanted to use the model with another sort of view, we’d be out of luck. In the BasicMVC directory, we start to remedy this. In particular, we want to separate out all the parts of the code that mutate the model, and isolate them within a controller.
To do this, we create a `Controller` class that takes in the model and the view — at their interface types, not at their concrete class types. We revise the view so that it no longer has access to the model at all. (This is overly drastic; we merely want to ensure that the view does not have *mutable* access to the model. We can revisit this later.) We next add a method to the view interface, `void setListener(ActionListener)`, which is the key indirection needed here. Instead of the view directly implementing the response to events, this method allows the view to take in a listener object and *forward* any events it receives to that listener.
\```java
public class JFrameView extends JFrame implements IView {
public JFrameView(String caption) { // NOTE: No model!
...
echoButton = new JButton("Echo"); // NOTE: No action listener
echoButton.setActionCommand("Echo Button");
this.add(echoButton);
exitButton = new JButton("Exit");
exitButton.setActionCommand("Exit Button");
this.add(exitButton);
...
}
public void setListener(ActionListener listener) {
echoButton.addActionListener(listener); // Rather than adding *this* as a listener,
exitButton.addActionListener(listener); // add the provided one instead.
}
...
}
public class Controller implements ActionListener {
public Controller(IModel m, IView v) {
this.model = m;
this.view = v;
view.setListener(this);
view.display();
}
@Override
public void actionPerformed(ActionEvent e) {
switch (e.getActionCommand()) {
case "Echo Button": ... // same code as before, but now
case "Exit Button": ... // it's extracted out of the view
}
}
}
\```
The controller is now the only part of the system that has mutable access to the model. Because it requested that the view register itself as the listener for the buttons, the controller gets called exactly when necessary, and it can decide what mutations to perform on the model. The view doesn’t even know that it’s received a controller object: as far as it’s aware, the controller is simply a random `ActionListener`. (**Note:** It is overly simplified to have the `Controller` directly implement `ActionListener` — after all, there might be many controls inside the view that could raise action events, and so having a *single* `ActionListener` isn’t a scalable approach. A better approach would be to have the `Controller` *have* one or more `ActionListener`s — i.e., preferring composition over inheritance — but we use this simplified form for now to reduce the number of classes in this example.)
**Note:** there is a subtle difference between the `setListener` method we’ve defined on our `IView` interface, and the `addActionListener` method present on the Swing components: our method’s name implicitly intends for only *one* listener at a time, but Swing components allow for multiple listeners. When we have multiple listeners, we’ll sometimes say that the control *broadcasts* its event to whoever’s listening, or that it *publishes* its event to whoever’s *subscribed* to it. There’s nothing limiting us from implementing this more general approach, but that generality wasn’t needed here.
## 6. Stage 3: Enhancing the view with keyboard support
Our next addition of functionality is shown in the KeyboardEvents directory: we want to add some keyboard-triggered behaviors. Specifically, we’ll add two fancy features to our UI: the ability to toggle the color of the text from black to red and back, and the ability to temporarily show the text in all-caps. We’ll switch colors every time we type the `'d'` key, and we’ll temporarily capitalize the text while we’re pressing and holding the `'c'` key. Interestingly, only one of these two new features requires adding a new method to our view interface.
First, we’ll need to generalize our `setListener` method, to take in a [`KeyListener`](https://docs.oracle.com/javase/8/docs/api/java/awt/event/KeyListener.html) as well as an `ActionListener`. A `KeyListener` is analogous to a `ActionListener`, but as the name suggests, it listens for keyboard-related events. There are *three* such events: when a key is pressed, when a key is typed, and when a key is released. Pressing and holding down a key for a while will typically generate one key-pressed event, several key-typed events, and one key-released event. Just as `ActionListener`s accept `ActionEvent`s, `KeyListener`s accept [`KeyEvent`s](https://docs.oracle.com/javase/8/docs/api/java/awt/event/KeyEvent.html) containing information about which key was involved. We’ll use the `keyTyped` event to toggle the color of the text, use the `keyPressed` event to capitalize the text, and use the `keyReleased` event to un-capitalize the text.
For now, we’ll simply have our controller implement the `KeyListener` interface also, and pass itself along as the second argument to `view.setListeners`. Again, note that the view doesn’t know or care that the exact same object is being passed in as the two distinct listeners: the types ensure that it doesn’t matter. (**Note:** And again, this is overly simplified, and we ought to have a distinct class implement this `KeyListener` interface, rather than have the `Controller` class do everything itself.)
To implement the color changing, we’ll need to add a method to our view interface to toggle the color of the text. This is intrinsically a view-specific thing, since the controller cannot know exactly how the text is displayed or which control it needs to change color. (That would leak internal implementation details of the view, and in any case, the controller only knows it has an `IView` rather than a particular view class.)
To implement the capitalization, note that we *do not* actually mutate the model! This is both a good thing and a necessary thing: suppose the model text contained a mixture of upper- and lower-case letters. If we mutated the model and capitalized everything, then we would not be able to undo that change later. Instead, we ask the model for its content, and inform the view that it should display a capitalized version of the that content. (This view-only change is analogous to “zooming in” while editing a picture in Photoshop or some other image editor: the view is technically displaying only a subset of the pixels of the document, and moreover is displaying them at far more than one screen pixel per document pixel! If “zooming in” actually mutated the document, then we’d lose information and be unable to “zoom out” again.)
## 7. Aside 1: Low-level and high-level events
Within the `KeyListener` interface, there is a qualitative difference between the key-pressed and key-released events, and the key-typed event. Individual key events are incredibly, tediously low-level. Just trying to type a capital `'A'` generates *five* events: the Shift key was pressed; the A key was pressed; the letter `'A'` was typed; the A key was released; the Shift key was released. (The last two events might happen in either order, depending on which key was released first.) Notice that only one key-typed event occurred, though, and it contained exactly the text that was typed.
If we had to deduce which keys were typed, merely from the key-pressed and key-released events, we’d quickly lose track. Java (and most GUI toolkits) thankfully translate those sequences into higher-level key-typed events. And this translation has an addtional benefit: consider typing non-English text on a typical QWERTY keyboard. We clearly need to type mutliple physical keys to produce one logical character (this is sometimes known as “chord” input, by analogy with pressing multiple keys on a piano keyboard), and this translation lets us ignore the individual key-pressed and key-released events if we only want to consider what text was typed.
On the other hand, if we want to keep track of which keys are pressed (e.g. to control a player’s motion while a key is held down), we need to resort to the lower-level events.
This low-level/high-level distinction is not clearly defined, and depends on perspective. Would we consider `ActionEvent`s to be low-level or high-level? On the one hand, they’re clearly much *higher-level* than individual [`MouseEvent`s](https://docs.oracle.com/javase/8/docs/api/java/awt/event/MouseEvent.html), which are analogous to `KeyEvent`s and indicate when a mouse button is pressed, released, or clicked, or when it enters or exits some area. Indeed, `JButton`s register themselves as `MouseListener`s, and translate the relevant mouse-clicked event into an `ActionEvent`! (They also register themselves as `KeyListener`s, and generate `ActionEvent`s when the Enter key is pressed.) But at the same time, the *user* of our view might not care which particular buttons we happened to use to implement the view, and there might well be multiple buttons that trigger identical actions: from that perspective, action events are too *low-level* and should be implementation details hidden behind some abstraction barrier.
Designing a view and controller properly requires considering what level of detail we want to expose in the events that the view forwards to the controller. Our current designs expose far too low-level detail: “something happened with the following action command”, or “some key was pressed/typed/released”. These events are very general, and have no specific semantics for our application. Let’s consider the different enhancements we can make, using either low-level or high-level events. We’ll find that we might want to translate generic low-level events into application-specific high-level ones.
## 8. Stage 4: Making the controller more flexible using low-level events
Many applications run on multiple systems (e.g. Windows, Mac and Linux), and have hotkeys to perform various common actions — but the exact hotkey differs on each platform. It would be a shame to have to recompile the code in order to support these different keys. Similarly, many applications allow the user to customize the hotkeys that control the application. Here, it’s outright impossible for users to recompile the program to customize its behavior! Our next generalization aims to alleviate this lack of flexibility.
Our prior attempt hard-coded the keys in the various key event-listeners. In the KeyboardMaps directory, we generalize this so that we can reconfigure hotkeys at runtime. To accomplish this, we design a new `KeyListener` implementation that uses dictionaries of `Runnable`s instead of `switch` statements in its event handlers. Specifically, our `KeyboardListener` will contain a `Map<Integer, Runnable>` dictionary for its key-pressed event handler, another such dictionary for its key-released handler, and a `Map<Character, Runnable>` dictionary for its key-typed handler. (These `Runnable`s are examples of the command pattern, which we talked about several lectures ago.)
The handlers are pleasingly straightforward:
\```java
// In the KeyboardListener class
@Override
public void keyTyped(KeyEvent e) {
if (keyTypedMap.containsKey(e.getKeyChar()))
keyTypedMap.get(e.getKeyChar()).run();
}
// analogously for the other two events
\```
Because the dictionaries are data, we can mutate them at runtime if we so choose, and therefore change which keys are mapped to which responses. (For variety’s sake, we show three different syntaxes for creating `Runnable`s: explicit classes, anonymous classes, and lambda expressions.)
\```java
// In the Controller class
private void configureKeyBoardListener() {
Map<Character, Runnable> keyTypes = new HashMap<>();
Map<Integer, Runnable> keyPresses = new HashMap<>();
Map<Integer, Runnable> keyReleases = new HashMap<>();
// Uses an explicit function-object class to provide the implementation
keyReleases.put(KeyEvent.VK_C, new MakeOriginalCase());
// Uses an anonymous object to provide the implementation
keyPresses.put(KeyEvent.VK_C, new Runnable() {
public void run() {
String text = model.getString();
text = text.toUpperCase();
view.setEchoOutput(text);
}
});
// Uses lambda-syntax to provide the implementation
keyTypes.put('r', () -> { view.toggleColor(); });
KeyboardListener kbd = new KeyboardListener();
kbd.setKeyTypedMap(keyTypes);
kbd.setKeyPressedMap(keyPresses);
kbd.setKeyReleasedMap(keyReleases);
this.view.addKeyListener(kbd);
}
\```
(In the same manner as this `KeyboardListener`, our implementation also generalizes the `ActionListener` implementation into a dictionary that maps action commands to `Runnable`s.)
This transformation, which converts *explicit control flow* (the `switch` statements we had earlier) into *data* (the keys of the `Map`) is a very common one in programming. We’ve clearly improved things somewhat, but we’re not finished: after all, if we hard-code the keys of the `Map` (as we did in the calls to `keyPresses.put` above), we haven’t really fixed anything, right? Fortunately, we know a way around this: by using a combination of a *reader* and *builder*, we could read in a “user preferences file” to figure out what keys to put in the maps, and then our code is completely agnostic to exactly which keys are needed for which handlers.
## 9. Stage 5: Decoupling the view from the controller using high-level events
The previous generalization relied on the view exposing its low-level events to the controller. However, we might reasonably want to trigger the same behavior from multiple UI controls. In the GeneralCommandCallbacks directory, we take this approach: our view can toggle the color of the text either via a button, or via typing the `'t'` key. (This is a simplified example, but is a stand-in for typical toolbar buttons doing the same thing as hotkey shortcuts.)
The key innovation in this design is that we’ve eliminated the `ActionListener`s and `KeyListener`s from the controller and *also* from the `IView` interface. Instead, we have a new `addFeatures` method that takes in a new interface, `Features`, whose methods are the various high-level features and abilities of our view. Our *prior* interfaces exposed low-level events saying, for instance, “Hey, a button with this action command has been clicked; what should be done?” These *new* callbacks say, for example, “The user has requested to make the display uppercase; what should be done?” This interface is bigger than the `ActionListener` interface, but it’s also much more application-specific, and it advertises exactly the responsibilities a controller has to uphold for this view. It also successfully encapsulates the action commands that we leaked in prior designs: the view is now free to change those commands without breaking the controller at all.
This design is quite elegant, and does the best job of loosening the coupling between the view and the controller: by encapsulating the details of which physical controls do what, the view is much more abstracted away, and the logical interface that it presents to the controller is much more application-specific. Note that the `Controller` class and the `Features` interface know absolutely nothing about Swing: they don’t even `import` the packages! Just like our model doesn’t need to know about how it’s being displayed, so too our controller doesn’t really need to know how the views are implemented. When possible, aspire to interfaces like this one, but be prepared to fall back to lower-level events when necessary.
**Note:** It might seem odd that the `toggleColor` method is both a callback on the `Features` interface *and* a method on the `IView` interface — why can’t the view handle this internally? Indeed, it possibly could! (And in some circumstances, the view definitely *should* handle some events entirely internally: for instance, when implementing a text editor, the view should probably internally handle the sequence of low-level events that indicate the user has selected a stretch of text. Once the user does something with that text — deletes it, replaces it, copies it, etc. — the view can raise a semantically relevant high-level event with the selected content.) But consider a “bigger” version of our current program, where we have *two* views that we want to keep synchronized: if either of them has toggled its color, both should toggle their colors together. The only way to ensure this synchronization is for the view to forward the event to the controller, which can in turn decide to tell *both* views how to update themselves.
**Note:** Once again, in the sample code and in the snippets below, we write that the `Controller` `implements` `Features` directly: we use an is-a relationship. A better approach is to use a has-a relationship, and have a dedicated object that implements the `Features` interface instead. We omit this variant here, because there are enough other details to focus on right now!
## 10. Aside 2: Designing a Features interface
In the previous section we introduced a `Features` interface, that hid the *Swing*-specific event listeners and instead exposed *application*-specific events. But how are we to know what sort of application-specific events we should have? It’s difficult to give a one-size-fits-all answer here, since by definition this will be rather application-specific. Instead, we can articulate some guidelines to follow:
- Avoid any view-specific classes in the `Features` interface wherever possible. Above, we said to eliminate any mention of Swing-specific events, and generalized it further so that the controller did not need to depend on the Swing library at all. We can easily come up with a plausible counterexample to this generality: suppose we were building a drawing program, and the view allows the user to choose a color to draw with. Surely the `Features` interface should expose the `java.awt.Color` that the user picked? In this case, though, the *model* also depends on `java.awt.Color`, and so the view isn’t introducing any new dependencies. If the model chose a different class to represent its colors, then the `Features` interface ought to expose *that*, rather than the Swing color directly.
- Treat the methods in the `Features` interface as *requests from the user*. In particular, a view might allow a user to attempt various editing operations that the controller might prohibit, or that the model might reject as invalid. In our example above, “requesting an uppercase display” fits into this mindset. Additionally, brainstorming “what might the user try to do?” can help articulate all the possible features a user might want from the program, which in turn can help design the `Features` methods needed.
- Not all view-specific events need to turn into `Features` requests. However, events that never make it to the controller are therefore intrinsically uncontrollable. In sophisticated GUIs, a single controller might need to synchronize multiple views: for instance, a [split-screen text editor](https://camo.githubusercontent.com/c535bd502fea4873724e2bebc144d3e2f4fe075e/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f32352f3131393038312f37333938343237612d366339362d313165322d393464632d3839363462333237333131332e706e67) could have two views of the same file, whose contents should be changed in tandem. A [video editor](https://helpx.adobe.com/content/dam/help/en/after-effects/how-to/camera-animation/jcr_content/main-pars/image_1685031719/camera-animation_step2.jpg) might have a timeline view at the bottom of the screen, as well as controls for the effects and a preview window, all of which must stay consistent. A [spreadsheet](https://www.exceldashboardtemplates.com/wp-content/uploads/2017/05/Copy-an-Excel-Chart-on-Same-Worksheet-Keeps-Same-Data-Reference-Final-Copied-Charts.png) might have a grid showing many cells of data, a textbox to edit a single cell, and a graph of multiple cells’ data, again which all must stay consistent. When implementing event handlers inside a particular view, consider whether the change being handled might need to be synchronized with multiple views: if so, then it should be raised as a `Features` request. If not, then it *might* be entirely handled within the view. For instance, scrolling a single text file might not need to inform the controller that the file has been scrolled, and so scrolling could be handled entirely within the view. On the other hand, a [diff viewer](https://www.git-tower.com/blog/media/pages/posts/diff-tools-windows/037ea85d83-1690885042/meld.png) that compares two files might wnat to scroll both files in unison, and so any scroll changes would need to be raised to the controller’s attention so that it can synchronize both views.
- When a `Features` event is raised to the controller’s attention, the view *probably* shouldn’t mutate itself, but rather wait for the controller to tell it to do so — this is in keeping with thinking of features as requests rather than demands. We noted this above, where `toggleColor` was both a method in the `Features` interface and also a method on the view, and the controller’s implementation of the `toggleColor` method turned around and called the view’s method, and we said this might seem overly roundabout. We gave one example where this was needed for synchronization of multiple views. Another might be triggering some resource-limited effect (where the resource might be tracked by the model, like in-game bonuses, or the resource might be controller-specific, like live-streaming a video of a gameplay). The view shouldn’t modify itself until the controller (and possibly model too) has confirmed that the request can go through.
- Any event that is handled entirely within the view is intrinsically more difficult to test. We would have to synthesize the low-level UI events, which may be impractical or even impossible to do sufficiently. By decoupling the particular UI event that triggers behavior from the `Features` implementation that actually provides the behavior, we limit the hard-to-test code to just the “wiring” between the event listeners and the `Features` object. Since all the interesting behavior is now in the `Features` object, and since its methods depend only on application-specific concepts and types, we can easily create a mock view that triggers `Features` methods without needing the actual Swing events. This allows for test cases analogous to those we saw in [Lecture 8](https://course.ccs.neu.edu/cs3500/lec_controllers_mocks_notes.html), with all the benefits they bring.
## 11. Stage 6: Further enhancements using `InputMap`s and `ActionMap`s
Our last revision eliminated the flexibility of dynamically changing hotkeys and reverted to hard-coding the keys in the view’s implementation of its own key listener. Combining the flexibility of the `KeyboardListener` and its dictionaries, with the high-level events of the `Features` interface, is aesthetically tricky: who should control which keys do what? In some sense, it almost feels like the choice should be made not by our current controller, which is controlling a particular model and view, but rather to some hypothetical “application controller”, that controls the overall application. We’ve already encountered this in practice: in IntelliJ, there are both project-specific settings and application-wide settings, and different dialogs control those different features.
Additionally, implementing the keyboard-listening dictionaries is tedious: surely this functionality could be implemented once and for all, and we wouldn’t need to redo it in every single application?
The solution, once again, is a layer of indirection. We’ve seen above that Swing components have a notion of an *action command*, and that we can cause code to run depending on which action was triggered. Combining several of these actions together should seem reminiscent of the `KeyboardListener` dictionaries we just built. In fact, Swing has a concept of an [`ActionMap`](https://docs.oracle.com/javase/8/docs/api/javax/swing/ActionMap.html), which is essentially just such a dictionary from action commands to actions.[1](https://course.ccs.neu.edu/cs3500/lec_gui_basics_notes.html#(counter-()._(gentag._23._lecturegui_basics))) An `Action` in Swing has many potential subclasses; we’ll focus on the `AbstractAction` class, which we can subclass ourselves to create new action handlers. For instance, we might write
\```java
Action makeOriginalCase = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
String text = model.getString();
view.setEchoOutput(text);
}
};
Action makeCaps = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
String text = model.getString();
text = text.toUpperCase();
view.setEchoOutput(text);
}
};
Action toggleColor = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
view.toggleColor();
}
};
\```
This is all well and good, and now we have yet one more way of representing runnable bits of code, but how should we actually associate these actions with their keyboard triggers? First, we associate these `Action`s with their action commands:
\```java
// Somewhere in our View class:
aComponent.getActionMap().put("restoreLowercase", makeOriginalCase);
aComponent.getActionMap().put("makeCaps", makeCaps);
aComponent.getActionMap().put("toggleColor", toggleColor);
\```
We then need the second piece of the indirection: an [`InputMap`](https://docs.oracle.com/javase/8/docs/api/javax/swing/InputMap.html). `InputMap`s associate [`KeyStroke`s](https://docs.oracle.com/javase/8/docs/api/javax/swing/KeyStroke.html) with action commands. A `KeyStroke` describes our key-press, key-release, and key-type low-level events in a uniform manner. When a low-level key event matches something in an `InputMap`, the associated action command is looked up in the `ActionMap` and fired. So we can continue our code as follows:
\```java
// Somewhere in our View class:
// Use simplest constructor to describe a key-press
aComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C), "makeCaps");
// Use another constructor to describe key-releases
aComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, true), "restoreLowercase");
// Yet another constructor lets us describe key-typed events
aComponent.getInputMap().put(KeyStroke.getKeyStroke("typed r"), "toggleColor");
\```
**Note:** `InputMap`s and `ActionMap`s apply only to `JComponent`s, and not to `JFrame`s directly. However, we can easily add a component to our frame for the sole purpose of handling key events, and we [can specify](https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) that its `InputMap` be used whenever the frame has focus at all.
This seems like a lot of machinery to introduce, and it would be unwieldy if it weren’t so reusable. The critical design patterns here are:
- We have a mechanism for translating low-level (`KeyEvent`s) into mid-level action commands.
- We have transformed control flow (of what code to run based on what key was pressed) into data (a map of `KeyStroke`s).
- We have a mechanism from translating mid-level action commands into actual runnable actions.
The code still suffers from one problem: the `Action`s themselves feel like they belong in the controller (since they refer to `model` and `view`), but are referenced directly in the view (in the `getActionMap().put(...)` lines). We can combine these maps with our `Features` design, to get one final translation from mid-level action commands to high-level events:
\```java
// NOTE: this class makes no mention of Actions or Controllers.
public class JFrameView extends JFrame implements IView {
KeyComponent keyComponent;
public JFrameView(String caption) {
super(caption);
...
this.keyComponent = new KeyComponent();
this.add(keyComponent);
}
public void setHotKey(KeyStroke key, String featureName) {
this.keyComponent.getInputMap().put(key, featureName);
}
public void addFeatures(Features features) {
this.keyComponent.addFeatures(features);
}
}
// NOTE: Neither does this class! It only refers to Features.
class KeyComponent extends JPanel {
List<Features> featureListeners = new ArrayList<>();
// Includes this new feature listener in responding to keys
void addFeatures(Features f) { this.featureListeners.add(f); }
KeyComponent() {
// Install action command -> Feature callback associations
this.getActionMap().put("makeCaps", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
for (Features f : featureListeners)
f.makeUppercase();
}
});
this.getActionMap().put("restoreLowercase", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
for (Features f : featureListeners)
f.restoreLowercase();
}
});
this.getActionMap().put("toggleColor", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
for (Features f : featureListeners)
f.toggleColor();
}
});
...
}
}
// NOTE: Only the Controller needs to know how to implement Features.
public class Controller implements Features {
private IModel model;
private IView view;
...
// These are the high-level event handlers we care about:
@Override
public void toggleColor() {
view.toggleColor();
}
@Override
public void makeUppercase() {
String text = model.getString();
text = text.toUpperCase();
view.setEchoOutput(text);
}
@Override
public void restoreLowercase() {
String text = model.getString();
view.setEchoOutput(text);
}
// This attaches this controller as our features listener
public void setView(IView v) {
this.view = v;
view.addFeatures(this);
// Choose the keys we want
view.setHotKey(KeyStroke.getKeyStroke("pressed c"), "makeCaps");
view.setHotKey(KeyStroke.getKeyStroke("released c"), "restoreLowercase");
view.setHotKey(KeyStroke.getKeyStroke("typed r"), "toggleColor");
}
...
}
\```
## 12. Exercises
At the top of this lecture are starter files for a GUI version of the Turtles example we worked through in [Lecture 10](https://course.ccs.neu.edu/cs3500/lec_commands_notes.html). The `TurtleGraphicsView` does not currently do anything. Enhance this code with a new `TurtlePanel` class that `extends JPanel`, and override its `paintComponent` method to draw whatever you want, just to confirm that it works.
Next, enhance the `IView` interface so you can pass the relevant information from the model, through the controller, into the view and into your `TurtlePanel` class. Once you’ve connected the pieces, use this information in your `paintComponent` implementation to draw the turtle’s trace.
The links at the top of the lecture include a “solution” implementation; do not to look at that until you’ve tried to implement this yourself.
The `IView` interface contains one method for setting up an event listener. What is its signature? Does it seem like a high-level event to you, or a low-level one? If you think it’s too low-level, can you think of a better, higher-level signature to use? If you think it’s sufficiently high-level, why do you think so?
1`Action`s themselves are more generally useful in Swing than just for keyboard interaction, but we’ll focus on just the keyboard part here.
你好,我是悦创。
飞雪连天射白鹿,笑书神侠倚碧鸳。金庸老先生的著作人物众多,武学精妙绝伦,剧情跌宕起伏,非常吸引人。我在上大学时,曾经两天时间就读完了《射雕英雄传》。
恰逢最近 DeepSeek 模型爆火,“模型蒸馏”这个专业名词也频繁出现在大众视野,所以在前置课程里面,我想先借着《射雕英雄传》里的人物,来聊聊“模型蒸馏”。不过你不用担心它过于深奥,作为应用开发者,我们只要知道它大致的原理就足够了。
然后呢,我想和你聊聊普通程序员怎么迎接 DeepSeek 的东风,可以用它帮我们做那些事儿。还会分享一下课程学习方法和建议,让你轻装上阵,为后续课程的学习打好基础。