版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、 ?程序语言综合课程设计?设计报告 学院:专业班级:学号:姓名:指导老师:完成时间: 目 录记忆测试系统11题目简介12设计的内容、要求和目标23总体设计34详细设计及实现54.1操作界面设计54.2类成员变量及方法设计64.3* 数据库设计74.4源代码分析85系统部署及使用说明14记忆测试系统1 题目简介记忆测试系统通过回忆法测试记忆能力,测试手段分为初级,中级,高级,三个级别2 设计的内容、要求和目标我设计的记忆测试系统,除了要编写的6个java文件所给的类外,还需在添加一些功能,如添加图标,改良测试条件,添加音乐功能,使程序有翻开相同的图标是播放一段音乐,不同时播放另一段音乐等。列出菜
2、单,有选择级别,选择图标,成绩记忆榜;在菜单下有M*N个图标随机分布,当点击一个图标,被保存下来,点击下一个图标时与上一个图标比拟,图标相同而方块不同那么success加1,反之那么不变,当点击六个相同的图标时记录所需的时间,保存在成绩记忆排行榜中;点击成绩排行榜,可登陆成绩,且会给成绩排行,由高到低。设计要求:(1) 单击“选择级别菜单可以选择初级,中级,高级。(2) 单击“查看排行榜菜单可以查看记忆榜,通过记忆榜存储每个级别的成绩(3) 选择级别后,将出项相应级别的测试区。测试区由M*N个方块组成程序通过设置M和N的值来确定相应的测试级别,例如,对于初级级别,M=5,N=6.每个方块上都有
3、一个图标,但该图标默认不可见。用鼠标当即测试区中的任何一个方块,那么计时器开始启动,同时该方块上的图标将显示出来。(4) M*N个方块组成的测试区中有M个图标,每个图标重复出现N次,并且随机分布在测试区中的M*N个方块上。用户必须用鼠标连续单击出N个图标相同的方块,也就是说,如果用户已经用鼠标连续单击出P个图标相同的方块,当再单击另一个方块时,如果该方块的图标和上一个的相同,那么该方块的图标和前P次单击出的方块的图标仍然可见;否那么前P次单击出的方块的图标全都变得不可见,只有当前单击的方块的图标可见。(5) 测试区能显示用户的用时,并根据级别的不同,提示用户必须用鼠标连续单击出多少个图标相同的
4、方块。(6) 测试区有提示图标所在位置的功能。用户单击测试区中的“提示按钮后,测试区将持续1.2秒为用户显示全部方块上的图标。用户每单击一次“提示按钮,用户的用时将增加10秒。(7) 连续单击出级别所要求的假设干个图标相同的方块后,将弹出保存成绩的对话框,用户可以通过该对话框选择是否将自己的成绩保存到成绩表中。(8) 单击“选择图标菜单可重新选择方块的图标样式,即可以重新选择M个图标。设计目标:可以选择级别,图标,当用户游戏结束后可以选择是否保存成绩,连续点击六个相同的图标可见,连续相同的图标播放一段音乐,不同播放另一段音乐。当用户不清楚相同图标的位置时可以按“提示按钮,将会对用户进行提示信息
5、。3 总体设计在测试系统中,可以编写6个java源文件:MenoryGame.java ,MenoryTestArea.java,Block.java,People.java,Record.java和ShowRecordDialog.java。记忆测试系统除了上述7个Java源文件产生的类外,还需要Java系统提供的一些重要的类。记忆测试系统所用到的一些重要的类以及之间的组合关系如图1.1所示。MemoryGameStringgFileMemoryTestAreaShowRecordDialogFileTreeSet<People>StringRecordThreadArrayLi
6、st<Block>TimerFile 图1.1 类之间的组合关系MenoryGame类负责创立记忆测试系统的主窗口,该类含有main方法,记忆测试系统从该类开始执行。MenoryGame类有四种重要类型的对象,分别是String,Flie,MenoryTestArea和ShowRecordDialog对象。MenoryGame类创立的窗口对象以及其中的主要成员对象。MenoryGame类的主要成员的作用将在后面的详细设计中阐述。MenoryTestArea类是主类MenoryGame窗口中的一个Jpanel容器,所创立的对象称为测试区,这个测试区被添加到MenoryGame窗口的中
7、心。MenoryTestArea类主要有六个种类型的成员:ArrayList<Block>,String ,Record,Timer,File和Thread对象。MenoryTestArea类的主要成员的作用将在后面的详细的设计中阐述。Block类是Jbutton的子类,负责为MenoryTestArea类中的ArrayList<Block>数组表提供Block对象。Redialog对话框的子类,当用户成功单击出相应的级别所要求的图标相同的方块后,Record创立的对象负责保存用户的名字和成绩到相应的级别文件中。dialog对话框的子类,当用户单击“查看排行榜时,Sho
8、wRecordDialog创立负责显示用户的成绩。People对象封装用户的名字和成绩,以便ShowRecordDialog对象可以按成绩的上下排序用户。4详细设计及实现4.1操作界面设计点击运行,选择级别,图标游戏开始,如果用户不清楚相同图标的位置时,点击提示。界面方案:窗口大小 :setBounds(100,100,400,360);setVisible(true);字体的大小和颜色hinMessage.setFont(new Font(“宋体,Font.BOLD,18);布局控制:又上到下,先是菜单:选择级别, 查看排行榜,选择图标;提示信息:hintMessage.setText(“您
9、需要用鼠标单击出+col+“个同样图标的方块);图标随机排布,有M*N个方块随机分布,在下面是一记录用户时间的还有提示按钮,提示用户相同图标的位置。 4.2类成员变量及方法设计一MemoryGame类1:成员变量l Bar是JmenuBar类创立的菜单条,可以向bar中添加菜单。l menuGradeBar和menuIcon是Jmenu类创立的三个菜单,这三个菜单的名称分别是“选择级别,“查看排行榜,“选择图标。l oneGradeItem,twoGradeItem和threeGradeItem是JmenuItem类创立的三个菜单项,被添加到menuGrade菜单中,这三个菜单项的名称分别是“
10、初级,“中级,“高级。l oneGradeResult,twoGradeResult和threeGradeResult是JmenuItem类创立的三个菜单项,被添加到menuResult菜单中。这三个菜单项的名称分别是“初级排行榜,“中级排行榜,“高级排行榜。l carImageIcon和animalImageIcon是JmenuItem类创立的两个菜单项,被添加到menuIcon菜单中。这两个菜单项的名称分别是“汽车图标,“动物图标l fileOneGrade,fileTwoGrade,filethreeGrade和gradeFile是File类创立的四个文件对象。fileOneGrade,
11、fileTwoGrade和filethreeGrade三个文件对象所引用的文件分别是“,“,“,分别用来写入三个级别的成绩。gradeFlie可以是fileOneGrade,fileTwoGrade和filethreeGrade中的某一个。l imageName是String类型的数组,每个单元是一个图像文件的名字。l menoryArea是测试区对象,由MenoryArea类负责创立。l showDialog是负责显示成绩的对话框,由ShowRecordDialog类负责创立l m和n是int型数据,其值可以确定相应级别中测试区中方块的数量,例如,对于中级水平,m=6,n=7,测试区一共有4
12、2方块。2:方法l actionPerformedActionEvent方法是MenoryGame类实现的ActionListener接口中的方法,负责执行菜单项发出的有关命令,。用户选择菜单中的菜单项可触发ActionEvent事件,导致actionPerformedActionEvent方法执行相应的操作。l MenoryGame是构造方法,负责完成窗口的初始化。l main(String)方法是软件运行的入口方法。二MemoryTestArea类1成员变量l row和col的值确定测试区中方块的数量。MemoryTestArea类创立的对象memoryArea是MemoryGame中的成
13、员,memoryArea对象通过调用initBlockint,int,String【】,File方法将MemoryGame类中m和n的值传递给row和col。l usedTime是用户的用时单位是秒。l success的值是用户找到的具有同样的图标的方块的个数l gradeFiel是级别文件,memoryArea对象通过调用initBlockint,int,String【】,File方法将MemoryGame类中的gradeFile的引用传递给MemoryGame类中的gradeFile的引用传递给memoryArea对象中的gradeFilel 数组表allBlockList单元的个数是ro
14、w和col的乘积,它的每个单元存放着一个Block对象。memoryArea对象在调用initBlockint,int,String【】,File方法时完成对allBlockList单元的初始化,即创立单元中的Block对象。l 字符串数组imageFileName中的每个单元是一幅图像文件的名字。memoryArea对象通过调用setIMageNameString【】方法将MemoryGame中存放图像文件名字的imageName数组的引用传递给imageFileName。l 链表openIconList用来存放用户找到图标相同的方块上的图标l 链表openBlockList用来存放用户找到
15、图标相同的方块。l hintThread是用Thread类创立的线程对象,用来提示测试区中的Block上的图标,提示方式是将测试区中的各个Block上图标持续显示1200毫秒。l hintButton是注册了ActionEvent事件的监视器,当单击它时,启动hintThread线程。l showUsedTime显示用户的用时,即显示usedTime的值。l hintMessage根据不同的级别显示相应的提示信息,如该级别需要用户寻找的相同图标方块的数目。l timer是计时器,负责改变usedTime的值。l record负责提供保存成绩的界面,是一个对话框,默认不可见。当用户寻找出级别所要
16、求的相同图标方块的数目后,该对话框可见,用户可以在该对话框中输入姓名,并保存所输入的姓名和usedTime的值到gradeFile指定的级别文件中。2方法l MemoryTestArea是构造方法,创立memoryArea对象时需使用该构造方法。l initBlockint,int,String【】,File方法。memoryArea对象调用该方法将参数的值传递给row,col,imageFileName和gradeFile,并依据这些值设置allBlockListList数组表的大小,然后创立allBlockList的单元中的Block对象,并设置Block对象上的图标。l setImage
17、NameString方法。memoryArea对象调用该方法可以将MemoryGame中的存放图像文件名字的imageName数组的引用传递给imageFileName。当用户单击MemoryGame主类窗口的carImageIcon和animalImageIcon菜单项时,memoryArea对象将调用该方法把存放图像文件名字的imageName数组的引用传递给imageFileName。l actionPerformedActionEvent方法。该方法是MemoryARea类实现的ActionListener接口中的方法。memoryArea中的每个Block对象都注册了ActionEv
18、ent事件监听器,当用户单击memoryArea中的某个Block对象时,actionPerformedActionEVent方法将被调用执行,所执行的主要操作是:如果该Block对象未显示图标,并且该Block对象设置的图标和openIconList的中存放的图标相同,就将该图标添加到链表opemIconList中,同时将该Block对象上的图标显示出来,并将success的值增1;如果该Block对象未显示图标,并且该Block对象设置的图标和openIconList的中存放的图标不相同,就将openIconList清空,然后再将该图标添加到链表opemIconList中,同时将该Bloc
19、k对象上的图标显示出来,并将success的值设置成1.当用户单击hintButton按钮时,actionPerfomedActionEVent方法将被调用执行,执行的主要操作是启动hintThread线程。l run方法。该方法是MemoryARea类实现的Runnable接口中的方法,启动hintThread线程后,该方法将执行,其主要操作是将测试区中的未显示图标的各个Block对象上的图标持续显示1200毫秒。三Blcok类 1成员变量 openStateIcon是方块上的图标 2方法 l Block对象调用setOpenStateIconImageIcon方法可以设置它上面的图标。l
20、Block对象调用getOpenStateIcon方法可以获得它上面的图标四ShowRecordDialog类 1成员变量l gradeFile是对话框要读取的文件,该文件存储成绩l clear是注册了ActionEVent事件的按钮。l showArea文本区显示对话框从gradeFile文件中读出的成绩l treeSet负责将成绩按上下排序 2方法l ShowRecordDialog是构造方法,负责创立showDialog对象l setGradeFileFile方法。ShowRecordDialog类创立的ShowRecordDialog对话框是主类MemoryGame窗口中的一个成员。当
21、用户选择窗口上的“查看排行榜菜单中的菜单项是,ShowRecordDialog对话框调用setGradeFileFile方法将相应的级别文件传递给gradeFile。l showRecord方法。ShowRecordDialog对话框调用该方法读取gradeFile文件中的成绩,为了将成绩按上下顺序显示在showArea文本区中,showRecord方法根据读取的名字和该名字的对应成绩,创立一个People对象存放在treeSet数集中。l actionPerformedActionEVent是ZctionListener接口中的方法,clear注册了ActionEVent事件,当用户单击cl
22、ear按钮时,actionPerform(ActionEvent)方法被调用执行,所执行的操作是去除gradeFile文件中的内容。五People类1成员变量l name是用户的名字l time是用户的用时2方法l getTime方法返回timel getName方法返回namel compareToObject是Comparable接口中的方法,其操作是确定People对象的大小关系。(六)Record类1成员变量l Time是用户的用时。l yourName是用户用来输入名字的文本框。l enter和cancel是按钮对象,用户单击center按钮可以保存成绩,单击cancel按钮可以放弃
23、保存成绩。l gradeFile是级别文件。2方法l Record是构造方法,负责创立record对象。l setGradeFile(File)方法。Record类创立的Record对话框测试区MemoryTestArea中的一个成员。当用户成功单击出相应级别所要求的图标相同的方块后,测试区弹出Record对话框,Record对话框将调用setGradeFile(File)方法将测试区中的级别文件传递给Record对话框中的gradeFile。l setTime(int)方法。当用户成功单击出相应级别所要求的图标相同的方块后,测试区弹出Record对话框,Record对话框将调用setTime
24、(int)方法将测试区记录的用户用时传递个Record对话框中的time。l actionPerformed(ActionEvent)是ActionListener接口中的方法,enter和cancel按钮注册了ActionEvent事件,当用户单击enter时,actionPerformed(ActionEvent)方法别提调用执行,其操作时将用户在文本框youName中输入的名字以及time的只保存到gradeFile文件中,然后关闭当前对话框;当用户单击cancer按钮时,actionPerformed(ActionEvent)方法别调用执行,其操作及时关闭当前对话框。4.3源代码分析M
25、emoryGameimport javax.swing.*;import java.awt.*;import java.awt.event.*;import java.io.*;import .MalformedURLException;import java.util.LinkedList;public class MemoryGame extends JFrame implements ActionListener JMenuBar bar;/创立菜单条 JMenu menuGrade,menuResult,menuIcon; JMenuItem oneGradeItem,twoGrade
26、Item,threeGradeItem; JMenuItem oneGradeResult,twoGradeResult,threeGradeResult; JMenuItem carImageIcon,animalImageIcon,lmImageIcon; File fileOneGrade,fileTwoGrade,fileThreeGrade,gradeFile; String imageName; MemoryTestArea memoryArea;/定义MemoryTestArea 对象 ShowRecordDialog showDiolag=null;/显示成绩对话框 int m
27、=5,n=6; final int imageNumber=7; /图片的个数 MemoryGame() throws MalformedURLException fileOneGrade=new File("初级记忆排行榜.txt"); fileTwoGrade=new File("中级记忆排行榜.txt"); fileThreeGrade=new File("高级记忆排行榜.txt"); bar=new JMenuBar(); menuGrade=new JMenu("选择级别"); oneGradeItem=
28、new JMenuItem("初级"); twoGradeItem=new JMenuItem("中级"); threeGradeItem=new JMenuItem("高级"); menuGrade.add(oneGradeItem); menuGrade.add(twoGradeItem); menuGrade.add(threeGradeItem); menuResult=new JMenu("查看排行榜"); oneGradeResult=new JMenuItem("初级排行榜");
29、twoGradeResult=new JMenuItem("中级排行榜"); threeGradeResult=new JMenuItem("高级排行榜"); menuResult.add(oneGradeResult); menuResult.add(twoGradeResult); menuResult.add(threeGradeResult); menuIcon=new JMenu("选择图标"); carImageIcon=new JMenuItem("汽车图标"); animalImageIcon=ne
30、w JMenuItem("动物图标"); lmImageIcon=new JMenuItem("龙马的图标"); animalImageIcon.addActionListener(this);/添加事件监听器 carImageIcon.addActionListener(this); lmImageIcon.addActionListener(this); menuIcon.add(carImageIcon); menuIcon.add(animalImageIcon); menuIcon.add(lmImageIcon); bar.add(menuG
31、rade); bar.add(menuResult); bar.add(menuIcon); setJMenuBar(bar);/设置菜单 oneGradeItem.addActionListener(this); twoGradeItem.addActionListener(this); threeGradeItem.addActionListener(this); oneGradeResult.addActionListener(this); twoGradeResult.addActionListener(this); threeGradeResult.addActionListener
32、(this); if(!fileOneGrade.exists() try fileOneGrade.createNewFile(); catch(IOException exp) if(!fileTwoGrade.exists() try fileTwoGrade.createNewFile(); catch(IOException exp) if(!fileThreeGrade.exists() try fileThreeGrade.createNewFile(); catch(IOException exp) setBounds(100,100,400,360); setVisible(
33、true); memoryArea=new MemoryTestArea(); imageName=new StringimageNumber; for(int i=0;i<imageName.length;i+) imageNamei=new String("car"+i+".jpg"); m=5; n=6; gradeFile=fileOneGrade; memoryArea.initBlock(m,n,imageName,gradeFile); add(memoryArea,BorderLayout.CENTER); showDiolag=n
34、ew ShowRecordDialog(); validate(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); public void actionPerformed(ActionEvent event)/实现ActionListener接口的方法,负责执行菜单项发出的有关命令 if(event.getSource()=oneGradeItem) m=5; n=6; gradeFile=fileOneGrade; memoryArea.initBlock(m,n,imageName,gradeFile); if(event.getSourc
35、e()=twoGradeItem) m=6; n=7; gradeFile=fileTwoGrade; memoryArea.initBlock(m,n,imageName,gradeFile); if(event.getSource()=threeGradeItem) m=7; n=8; gradeFile=fileThreeGrade; memoryArea.initBlock(m,n,imageName,gradeFile); if(event.getSource()=carImageIcon) for(int i=0;i<imageName.length;i+) imageNam
36、ei=new String("car"+i+".jpg"); memoryArea.setImageName(imageName); memoryArea.initBlock(m,n,imageName,gradeFile); if(event.getSource()=animalImageIcon) for(int i=0;i<imageName.length;i+) imageNamei=new String("ani"+i+".jpg"); memoryArea.setImageName(imageNa
37、me); memoryArea.initBlock(m,n,imageName,gradeFile); if(event.getSource()=lmImageIcon) for(int i=0;i<imageName.length;i+) imageNamei=new String("lm"+i+".jpg"); memoryArea.setImageName(imageName); memoryArea.initBlock(m,n,imageName,gradeFile); if(event.getSource()=oneGradeResult
38、) showDiolag.setGradeFile(fileOneGrade); showDiolag.showRecord(); showDiolag.setVisible(true); if(event.getSource()=twoGradeResult) showDiolag.setGradeFile(fileTwoGrade); showDiolag.showRecord(); showDiolag.setVisible(true); if(event.getSource()=threeGradeResult) showDiolag.setGradeFile(fileThreeGra
39、de); showDiolag.showRecord(); showDiolag.setVisible(true); public static void main(String args) throws MalformedURLException/软件运行的入口方法 new MemoryGame();/构造方法,负责完成窗口的初始化 MemoryTestAreapackage com.yangrang;import javax.swing.*; import java.applet.Applet;import java.applet.AudioClip;import java.awt.eve
40、nt.*;import java.awt.*;import java.util.*;import java.io.*;import .*;public class MemoryTestArea extends JPanel implements ActionListener,Runnable /实现ActionListener和,Runnable接口,所创立的对象 int row,col; File gradeFile; ArrayList<Block> allBlockList; String imageFileName; LinkedList<ImageIcon>
41、openIconList; LinkedList<Block> openBlockList;/java.util 的 类 LinkedList<E> 链接列表 主要方法为 /add(E e) 将指定元素添加到此列表的结尾。 int success=0; Thread hintThead;/用来提示测试区中的Block对象 JButton hintButton; int usedTime=0; JTextField showUsedTime,hintMessage; javax.swing.Timer timer; Record record; JPanel center
42、,south; MemoryTestArea() throws MalformedURLException setLayout(new BorderLayout(); allBlockList=new ArrayList<Block>(); openIconList=new LinkedList<ImageIcon>(); openBlockList=new LinkedList<Block>(); hintThead=new Thread(this); hintMessage=new JTextField(); hintMessage.setHorizon
43、talAlignment(JTextField.CENTER); hintMessage.setEditable(false); hintMessage.setFont(new Font("宋体",Font.BOLD,18); center=new JPanel(); south=new JPanel(); hintButton=new JButton("提示"); hintButton.addActionListener(this); showUsedTime=new JTextField(8); showUsedTime.setEditable(fa
44、lse); showUsedTime.setHorizontalAlignment(JTextField.CENTER); south.add(new JLabel("用时:") ; south.add(showUsedTime); south.add(new JLabel("提示图标位置(导致用时增加):") ; south.add(hintButton); add(south,BorderLayout.SOUTH); add(hintMessage,BorderLayout.NORTH); timer=new javax.swing.Timer(10
45、00,this); record=new Record(); /创立record文件,当用户成功完成时弹出对话框,写入record /* * 保存用户 游戏的游戏记录 */ /添加游戏的运行音乐 File musicFile=new File("run.wav"); URI uri=musicFile.toURI(); URL url=uri.toURL(); AudioClip clip=Applet.newAudioClip(url); clip.play(); /添加游戏 运行 音乐/ public void initBlock(int m,int n,String
46、name,File f) row=m; col=n; gradeFile=f; center.removeAll(); imageFileName=name; ImageIcon icon=new ImageIconimageFileName.length; for(int i=0;i<icon.length;i+) iconi=new ImageIcon(imageFileNamei); if(allBlockList.isEmpty() for(int i=0;i<row*col;i+) allBlockList.add(new Block(); else allBlockLi
47、st.clear(); for(int i=0;i<row*col;i+) allBlockList.add(new Block(); for(int i=0;i<allBlockList.size();i+) allBlockList.get(i).addActionListener(this);/get()方法:get(int index)返回此列表中指定位置处的元素。 allBlockList.get(i).setOpenStateIcon(iconi%row); /*调用 Block 的方法 加载图片: void setOpenStateIcon(ImageIcon ico
48、n) openStateIcon=icon; */ Collections.shuffle(allBlockList); /随机排列allBlockList中的节点 center.setLayout(new GridLayout(row,col); for(int i=0;i<allBlockList.size();i+) center.add(allBlockList.get(i); add(center,BorderLayout.CENTER); if(timer.isRunning() timer.stop(); hintMessage.setText("您需要用鼠标单击出"+col+"个同样图标的方块"); usedTime=0; showUsedTime.setText(null); validate(); public void setImageName(String name) imageFileName=name; public void actionPerforme
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 房屋建筑工程清包合同格式
- 集资房指标转让协议书
- 婚前购房协议书范本
- 技术合作三方协议
- 国际快递服务协议范本
- 2024年购车借款协议书范本
- 股权分红协议书模板
- 二手购房合同协议范本
- 正规汽车租赁合同文本
- 2024年模范委托付款协议书
- 坐标纸(网格型坐标纸-直接打印即可)
- 占比分析类PPT模板
- GB/T 21633-2020掺混肥料(BB肥)
- GB/T 21435-2008相变加热炉
- 河南省洛阳市《综合能力测试》事业单位国考真题
- 智慧消防整体解决方案消防大数据一体化管理平台解课件
- 《推窗风来》中考语文作文优秀6篇
- 腹部血管疾病的超声诊断课件整理
- 《客源国概论》期末考试题
- 公司吸烟管理规定范文
- 《江苏省建筑业10项新技术(2021)》
评论
0/150
提交评论