BUAA-OO-第四单元总结——终章

 

面向对象第四单元博客总结——终章

第四单元作业设计

第13次作业设计

类和对应方法属性设计

  • 类设计如下图所示

    

  • 本次作业主要涉及六个类,其中包括主类 Main ,通用Map类 UmlElementIdMap 类,实现官方接口类 MyUmlInteraction 类,以及自定义interface类 MyInterface 类,自定义class类 ClassContainer 类,自定义Operation列表 MethodList 

UmlElemeIdMap类

  • 该类设计初衷是为了存储所有的UMLElement从Id到元素的映射,从而方便后续操作中对UMLElement的提取。因此为了实现该类的通用性,将该类实现为单例化模式,并且所有方法声明为静态方法,从而不用在其他每个类中存储该类的属性分量

  • 属性:

    •  private static final HashMap<String, UmlElement> map  存储UML图中所有的元素从Id到Element的映射集合
  • 方法:

    •  public static UmlElement put(String id, UmlElement e)  向该通用Map中加入新的UML元素
    •  public static UmlElement get(String id)  使用Id从该通用Map中提取UmlElement元素

MethodList类

  • 该类设计初衷是为了存储一个类中的所有方法,包括对应方法的相关信息,比如可见性,参数等等。即将所有parentId相同的UMLOperation存储在同一个 MethodList 中,从而方便后续Class信息的存储。

  • 属性:

    •  private final HashMap<OperationQueryType, HashSet<UmlOperation>> modeMap  记录对应参数属性的所有方法的Map,其中Map的Key为方法的参数属性,Value是一个存储对应方法的Set
    •  private final HashMap<String,HashMap<Visibility,HashSet<UmlOperation>>> methodVisiMap  记录对应方法名字的所有方法,包括可见性的双层Map。第一层Map的Key是方法名字,Value是第二层Map;第二层Map的Key是方法的可见性,包括PUBLIC,PRIVATE,PROTECTED,PACKAGE,Value是对应可见性的方法UMLOperation。
    •  private HashSet<UmlOperation> paramBuf  方法传入参数类型的buf,记录中途所有没有登记过传入参数的方法的Set,构造结束后会对Set进行清空,即全部加入无传入参数的Map中
    •  private HashSet<UmlOperation> returnBuf  方法返回参数类型的buf,记录中途所有没有登记过返回参数的方法的Set,构造结束后会对Set进行清空,即全部加入无返回参数的Map中
  • 方法:

    •  public void putMethod(UmlOperation method)  向MethodList新添加方法,即注册方法
    •  public void linkParam(UmlParameter param, UmlOperation parent)  注册参数类型,即对对应方法进行参数类型的注册,即修改两个buf以及向参数属性Map新添方法
    •  public void flush()  该方法在所有参数注册完毕后才调用,即对两个buf进行清空,加入到对应Map中
    •  public int methodCount(OperationQueryType mode)  查询方法,查询对应参数类型的方法总数
    •  public Map<Visibility,Integer> methodVisibilityCount(String name)  查询方法,查询对应名字的方法的可见性,返回一个所有可见性对应方法个数的Map供上级调用函数处理。

ClassContainer类

  • 该类设计初衷是为了存储对应Class的所有信息,包括所有注册的方法,父类,顶级父类,实现接口等待

  • 属性:

    •  private UmlClass mean  记录对应ClassContainer注册的UmlClass
    •  private ClassContainer parent  记录直接父类
    •  private ClassContainer topFather  记录顶级父类
    •  private MethodList myMethods  记录注册的所有方法,使用MethodList类进行存储
    •  private HashMap<String, Visibility> attributeMap  记录注册的所有属性,Key是属性名字,Value是属性的可见性
    •  private int allAttributes  记录所有属性的个数,包括继承得到的属性
    •  private HashSet<UmlAttribute> unSafeSet  记录所有不安全属性,即所有可见性违反安全性的属性
    •  private HashSet<MyInterface> interfaceSet  记录所有直接实现的接口
    •  private ArrayList<UmlElement> associateList  记录所有关联端
    •  private int associationCount  记录所有关联个数
    •  private HashSet<UmlClass> associateClassSet  记录所有关联的Class
  • 方法:

    •  public void addMethod  向该ClassContainer注册方法
    •  public void addAttribute  注册属性
    •  public void addAssociate  注册关联
    •  public void addInterface  注册实现的接口
    •  public void addFather  注册父类
    •  public int methodsCount  查询方法,查询对应类对应参数类型的所有方法,即查询其MethodList
    •  public int allAttributeCount  查询方法,递归查询所有的属性个数
    •  public UmlClass getTopFather  查询方法,递归查询顶级父类
    •  public Map<Visibility,Integer> getOperationVisibility  查询方法,查询对应方法名字的可见性列表
    •  public int getAssociationCount  查询方法,递归查询所有的关联个数
    •  public HashSet<UmlClass> getAssociateClassSet  查询方法,递归查询所有关联类的个数
    •  public HashSet<UmlAttribute> getUnSafeSet  查询方法,查询所有不安全属性的Set
    •  public HashSet<MyInterface> getInterfaceSet  间接查询方法,查询直接实现的接口

MyInterface类

  • 该类实现初衷是设计一个可以存储接口以及相关继承信息的自定义类,进而方便查询函数的书写

  • 属性:

    •  private HashSet<MyInterface> parent  注册接口的直接父接口
    •  private final UmlInterface mean  注册其注册的接口
  • 方法:

    •  public void addFather  向接口注册父接口
    •  public HashSet<MyInterface> getFather  查询方法,递归查询所有父接口

MyUMLInteraction类

  • 该类为实现官方接口类,主要属性为上述定义的自定义存储类,主要方法为查询方法,其中包括调用已经在属性中实现的方法以及间接实现的方法

  • 属性:

    •  private final HashMap<String, HashSet<UmlClass>> classMap  注册所有Class,Key是Class的名字,Value为对应名字的所有UmlClass
    •  private final HashMap<UmlClass,ClassContainer> umlClassMap  建立UmlClass到ClassContainer的映射,从而方便提取所有注册的ClassContainer
    •  private final HashMap<String, MyInterface> interfaceMap  注册所有的接口,Key是接口的Id,Value为对应Id的Myinterface
  • 方法:

    • 对于每一个UmlElement类型,存在对应的add方法,分别对每一个Element进行分类添加到对应数据结构里。例如对于 addUmlClass 方法将UmlClass新建一个ClassContainer对象并且注册到classMap中

    • 官方接口查询方法:

      • 除了需要递归查询的方法,不包括 getTopParentClass ,其余方法均直接调用子数据结构中已经实现的方法进行返回
      • 需要递归查询的方法,包括 getClassAttributeVisibility 等,在本类方法中实现递归的步骤,调用子数据结构获取本层递归的信息,最后将所有递归层信息进行整合后返回

算法考虑

  • 本次作业框架的构建考虑将不同的UMLElement查询分配给不同的数据结构完成,主类仅仅只是完成调用子数据结构的查询结果,并且按照一定逻辑进行综合。因此考虑对接口的查询交由 MyInterface 类递归完成,对于方法的查询交由 MethodList 类完成,对于类的相关查询,交由 ClassContainer 类完成,包括递归查询父类,以及调用 MethodList 查询方法返回结果。
  • 本次作业需要支持的UML图查询包括给定类名查询方法的类型;给定类名和属性名查询对应属性可见性;给定类名查询关联个数;给定类名查询接口实现个数。其中第一种查询只需要查询当前类当前所有方法的类型,因此不需要递归查找父类,只需要返回当前类的信息,因此考虑将此方法交给 MethodList 实现,而后两种方法需要递归查找所有父类的信息,而最后一种查询不仅需要递归查询所有父类,还需要递归查询实现的接口的所有父接口,因此考虑将父类的递归查询交由 ClassContainer 实现,接口的父接口递归查询交给 MyInterface 实现,而 MyUMLInteraction 类负责对两者查询结果进行综合之后返回。
  • 此外对于UML图构建数据结构的顺序,考虑到所有UMLElement输入的顺序可能是无序的,因此需要安排一定的顺序进行数据结构的构建,以防止出现不可预料的错误。因此规定先将所有的UMLElement分类,之后按类依次加入数据结构,完成构图
  • 对于结果缓存,除了查询类对应属性的可见性查询方法之外,所有查询方法均对结果进行了缓存,即在递归进行前判断是否已经查询过,若查询过则直接返回,否则查询。

第14次作业设计

框架设计

  • 各个包的类图如下,其中umlclass包完成UMLModel图的解析以及查询,umlsequence包完成UMLSequence的解析和查询,umlstate完成UMLStateMachine图的解析和查询,utils是完成本次作业的通用工具包
  • 对于包结构图如下:

    

MyUmlGeneralInteraction类

  • 该类实现官方接口,完成本次作业所有任务

  • 属性:

    •  private MyUmlClassModelInteraction classModel  记录类图模型
    •  private MyUmlSequenceModelInteraction sequenceModel  记录顺序图模型
    •  private MyUmlStateModelInteraction stateModel 记录状态图模型
  • 方法:

    • 对于官方接口查询方法,调用子数据结构的查询方法完成
    • 对于官方接口的检查方法,调用类图模型的检查方法完成

umlclass包

    

  • umlclass包中的类设计和上一次作业基本一致,增加了 RelationshipTree 泛型类,存储class和interface的继承关系树,以便完成类图构建完毕后的合法性检查;增加了 InterfaceDupExc 异常类,以便完成合法性检查

RelationshipTree类

  • 该类设计为泛型类,为了存储class和interface的继承关系,方便类图的合法性检查

  • 属性:

    •  private HashMap<T, HashSet<T>> fathers  存储对应元素的所有父亲,包括间接继承和直接继承的所有父亲,Key是对应的元素,Value是元素所有父亲的Set
    •  private HashMap<T, HashSet<T>> sons  存储对应元素的所有儿子,包括间接继承和直接继承的所有儿子,Key是对应元素,Value是元素的所有儿子的Set
    •  private HashSet<T> duplicateSet  存储重复继承的所有元素的Set
  • 方法:

    •  public void add(T element)  向对象中新添新的元素
    •  public void add(T son, T father)  向对象中新添新的元素以及新的继承关系,更新所有的Map包括duplicateSet
    •  public HashSet<T> getFathers(T son)  获取对应元素所有的父亲
    •  public HashSet<T> getSons(T father)  获取对应元素所有的儿子
    •  public HashSet<T> getCircleSet()  获取存在圈继承的所有元素
    •  public HashSet<T> getDuplicateSet()  获取存在重复继承的所有元素

umlsequence包

    

  • umlsequence包中的类设计主要完成顺序图的构建和查询功能

MyUmlInteraction类

  • 该类完成对于一个UMLInteraction的信息的存储以及查询,包括所有的LifeLine和Message

  • 属性:

    •  private UmlInteraction mean  存储注册的UMLInteraction
    •  private HashMap<String, HashSet<UmlMessage>> messageMap  存储对应的LifeLine的所有incoming信息,Key为LifeLineId,Value为存储UMLMessage的Set
    •  private HashMap<String,HashSet<String>> lifelineMap  存储从LifeLine名字到LifeLine的映射,Key为名字,Value为对应名字的LifeLineId的Set
    •  private HashSet<UmlLifeline> lifelineSet  存储所有注册的LifeLine
    •  private HashSet<UmlMessage> messageSet  存储所有注册的Message
  • 方法:

    • 对于顺序图中相关元素,即UMLMessage,UMLLifeLine都有对应的添加方法,添加到数据结构中存储
    •  public int getLifelineCount()  查询所有的LifeLine个数
    •  public int getMessagesCount()  查询所有的Message的个数
    •  public int getInComingMessageCount  查询对应名字的LifeLine的所有incoming信息

MyUmlSequenceModelInteraction类

  • 该类实现官方接口UmlCollaborationInteraction,完成对顺序图的构建以及查询

  • 属性:

    •  private HashMap<String,MyUmlInteraction> myInteractionMap  存储注册的所有UMLInteraction,Key为UMLInteractionId,Value为对应的MyUMLInteraction
    •  private HashMap<String, HashSet<String>> interactionMap  存储注册的所有UMLInteraction名字到Id的映射,Key为UMLInteraction名字,Value为对应的UMLInteractionSet
  • 方法:

    • 对于顺序图中的所有元素有对应的添加元素方法,完成对于数据结构的构建
    •  public int getParticipantCount  查询对应名字UMLInteraction的所有参与对象
    •  public int getMessageCount  查询对应名字UMLInteraction的所有Message
    •  public int getIncomingMessageCount  查询对应名字UMLInteraction的对应LifeLine的所有incoming信息

umlstate包  

    

MyUmlState类

  • 该类实现对State的封装,主要记录对应State的后继状态

  • 属性:

    •  private UmlElement mean  记录注册的UMLState
    •  private HashSet<MyUmlState> sons  记录所有的后继状态
  • 方法:

    •  public void addSon(MyUmlState son)  为对应的UMLState记录直接后继状态
    •  private void findSons  dfs查询所有后继状态
    •  public int getSonsCount()  调用查询方法并缓存结果

MyUmlStateMachine类

  • 该类实现对UMLStateMachine的封装,主要存储对应StateMachine的相关信息

  • 属性:

    •  private UmlStateMachine mean  记录注册的UMLStateMachine
    •  private HashSet<UmlState> stateSet  记录所有注册的UMLState
    •  private boolean initialState  记录是否注册了初始状态
    •  private boolean finalState  记录是否注册了末状态
    •  private HashSet<UmlTransition> transitionSet  记录所有注册的Transition
    •  private HashMap<String,HashSet<String>> stateMap  记录从StateName到StateId的映射
    •  private HashMap<String,MyUmlState> myUmlStateMap  记录StateId到MyUMLState的映射
  • 方法:

    • 对于状态图中对应元素的添加,包括UMLState,UMLPseudostate,UMLFinalState,UMLTransition等,都有对于的添加方法
    •  public int getStatesCount()  查询StateMachine注册的所有方法,包括initial和final
    •  public int getTransitionCount()  查询Transition的个数
    •  public int getStateSonsCount(String name)  查询对应名字的状态的所有后继状态的个数

MyUmlStateModelInteraction类

  • 该类实现了官方接口UmlStateChartInteraction,完成对状态图的注册和查询

  • 属性:

    •  private HashMap<String, HashSet<String>> stateMachineMap  记录从StateMachineName到StateMachineId的映射
    •  private HashMap<String, MyUmlStateMachine> myMachineMap  记录从StateMachineId到对应MyUMLStateMachine的映射
  • 方法:

    • 对于状态图中的每一个元素都有对应添加方法添加到数据结构中
    • 对于官方接口查询方法,调用对应子数据结构的查询方法完成

算法考虑

  • 对于本次作业框架设计,三种模型分别实例化对应的数据结构来存储信息,其中检查方法内嵌到类图模型中完成

  • 本次作业的算法难点主要在于状态图后继状态查询和类图模型检查

  • 对于状态图后继状态个数查询,使用dfs算法完成

  • 对于类图模型的合法性检查,调用对应 RelationshipTree 的方法进行检查:

    • 对于规则R002,若对应的类或接口的所有父亲中存在自己,则该类或接口处于某个圈中

    • 对于规则R003主要分成三个部分:

      • 对于接口重复继承,交给RelationshipTree统计并返回结果
      • 对于类重复实现接口,包括类当前层重复实现同一个接口;类在不同层实现同一接口;类实现重复继承的接口,对于这三种情况在类图模型中完成检查

四个单元构架设计演变

架构设计演变

第一单元——从面向过程到面向对象

  • 2019年3月份开始,我第一次正式接触面向对象编程语言—— Java ,虽然之前在Java课上已经用Java写过一些程序,但是都是以面向过程的思想去完成,并且对Java里定义的类这一概念理解的还并不够深入,写出来的程序自然就带有很重的面向过程的味道。第一次OO作业时,我开始尝试让自己理解面向对象这一概念,尝试理解将表达式当做一个具有实际意义的事物,使用代码的方式描绘这一事物的行为,因此尝试着去用封装这一概念来完成作业,设计了两个类,一个主类,一个多项式类。多项式类定义了多项式的各种行为,包括构造,加法,求导,乘法等。可以说在第一单元第一次作业,算是初步理解的面向对象编程的思想,成功做到了封装。
  • 但到了第二次作业,要考虑为表达式增加新的元素——三角函数。但是第一次作业仅仅只对表达式做了封装,没有对表达式内部拆分成更细的对象,因此代码基本不可重用,整一次作业需要重构。因此第二次作业,考虑到重用这一概念,将表达式对象再进行进一步的拆分,拆分出项,因子,表达式三个部分,设计了三个元素类,此外为优化表达式设计了表达式优化类。但是这一次作业重点并没有放在可重用设计上,还是花了大部分心思来优化上,因此为了优化方便,类的设计没有分的很细,并且设计了很多适用于优化操作的操作。因此还是带有很大的面向过程的味道。
  • 因此到了第三次作业付出了比较惨痛的代价,由于第二次作业考虑优化较多,类的设计没有细分,导致到第三次作业时为表达式增加括号因子时,整个第二次作业的内容又都不可用需要再次重用。第三次作业吸取了第二次作业的教训,没有将架构设计的重点放在优化上,而是放在可操作性上,对类设计进行了清晰地划分,并且运用了继承和多态这一特性,设计了抽象父类Factor类,五种因子(常数,幂函数,sin函数,cos函数,表达式因子)均继承于抽象父类,在表达式类中设计Factor类的一个ArrayList来存放这些不同的因子,从而方便了求导工作,不必为每一种因子设计一种属性。并且为了优化,设计了ExpressionBuilder类在该类实现所有的优化工作,而不在其他类中内嵌优化操作,进而实现了优化和构造解耦的过程。
  • 第一单元三次作业,我成功的从面向过程向面向对象进行过渡,尝试了封装,继承,多态三种面向对象编程的特性,算是正式入门了面向对象编程。

第二单元——结合实物设计考虑面向对象

  • 第二单元开始,开始尝试多线程编程,需要考虑的事项不仅仅有第一单元的封装继承多态,还要注意线程的安全性。为了更好的贯彻面向对象思想,在设计第二单元作业框架时,我将要编写的电梯程序中的电梯想象成真正的电梯实物,考虑电梯都具有什么控制单元进而来对类进行设计。对于第一次作业,首先设计了一个完全机械化的电梯,即只记录电梯信息包括人数,楼层等,此外再设计了一个调度控制器类,即电梯的脑子,通过观察不同楼层请求的情况分析需求分布,进而对电梯进行控制,完成电梯的活动。因而第二次作业完全沿用了第一次电梯作业的代码,仅仅添加了电梯调度器的优化功能。
  • 对于第三次作业,因为增加了不同电梯不同控制域这一概念,因此需要对请求进行拆分。考虑到现实生活中请求的本体都是人本身,因此应当将请求拆分交由独立的Person类来完成,而不是电梯来完成。因此独立完成了Person类,描述一个请求的拆分,并且定义了请求的拆分工作。此外为了线程的安全性,还单独设计了公共锁类,从而保证电梯安全工作。
  • 本单元架构的设计也开始使用包来对不同的架构进行分类,比如电梯方面的类设计放在elevatorsystem包内,请求类放在personrequest包内

第三单元——规格设计框架

  • 第三单元首次接触到了规格这一概念,所有的架构设计都是依据于规格。因此这一单元上的架构要求并不高,类的设计都是严格按照规格来完成。但是为了实现类的简洁性,将类中的部分细节从大类中分出来,单独实现小类来完成,而不会所有的功能均交给大类来实现而导致过于臃肿。例如在第三单元第三次作业中,为了方便最短路算法的实现,设计了Station类和Road类来存储节点和不同的边权。

第四单元——模型理解框架

  • 第四单元两次作业比较成功的做到了不同类型不同包的封装。对于第一次作业由于仅仅需要解析并且查询类图,因此所有的类均存在于一个包内。同样为了使主类不至于太臃肿,将部分信息分散的存储到子数据结构中,例如将UMLOperation的信息封装到MethodList中,将Class的信息封装到ClassContainer中,Interface的继承关系封装到MyInterface中,主类只需要调用这些子数据结构的方法完成设计。
  • 第二次作业由于需要解析的有三种模型,因此设计了三个包,每个包负责解析和查询一种UML图,主类只需要对三个包的类调用查询进而完成工作。

OO方法理解改变

从算法为重到框架为重

  • 从第一单元刚刚开始接触面向对象时,设计程序的核心还是放在算法设计上,所有类的设计都是依据于算法来设计的,因此类与类之间交流非常频繁,并且为了算法甚至放弃了类的细分,导致类内部设计很多写死而难以分开供下次作业重用。因此第三次作业由于第二次作业类的设计过于紧密因而导致难以解耦分离。
  • 因此后续作业吸取了前次的教训,将框架和算法分离,设计通用框架之后再考虑算法,将优化算法单独考虑为一个类,从而避免在通用类中交互信息来实现优化,导致耦合度过高难以分离。

认识并且运用规格

  • OO第三单元开始认识了规格这一概念,理解了面向对象编程不仅仅要设计类的结构,还要设计方法实现的功能,进而能更好的安排不同类之间的关系。因此在第三单元开始,面向对象不仅仅只是简单的类的封装,继承,多态,还有方法细节的设计,以及设计类与类之间的沟通,从而能够设计出简洁,完整的程序框架

测试理解与方法演变

测试理解

  • 测试是检验程序正确性的一种重要方式,主要测试按逻辑分有两种方式,分别是算法设计正确性测试,和程序运行时合法性测试。对于算法设计正确性测试主要进行在程序设计之前,对完成程序的方法需要事先设计一定算法,进而再有依据地去按照算法细节去完成程序。对于程序合法性设计是在程序运行过程中,使用测试数据对程序进行检验,不仅仅检验算法运算结果是否符合预期,还要检验是否在运行过程中出现不可预料的错误情况发生。
  • 对于第一种算法正确性的检查,需要在书写程序之前确定方法的规格,在书写程序后对于每个方法检查程序语句集合组成的序列是否能够完成规格设计达到的预期需求。这一种检查方式比较耗时,并且不容易发现代码中的bug,但是是能够从根本上保证程序正确性的一种测试方式。
  • 对于第二种测试,需要构造大量测试集合来对程序进行验证。测试集合也并不是完全随机无规律的,需要存在一定针对性针对方法进行检验。这一测试方式能够通过使用大量数据集合来保证程序的正确性,并且也能够实现一系列自动化过程,而不用耗费大量人力来检查,但是要构造不同针对性的大量数据集合也不是非常容易,如果数据集合覆盖面不够广,也可能导致测试出现疏漏。

单元测试和综合测试

  • 为了更好的完成第一种方式的测试,并且为了能够在编写程序过程中对程序进行测试,即针对方法进行独立的测试,就需要使用到单元测试。这一学期面向对象课程中第一次接触到单元测试的是第三单元规格设计。对于每一个方法需要使用具有一定针对性的数据测试方法和基础功能和合法性。但由于程序总体没有完成,因此需要使用单元测试来检验方法的基础功能。在第三单元我是用Junit来对方法的基础功能进行测试。
  • 单元测试仅仅只能够测试方法的基础功能,对于整个程序总体的正确性以及稳定性,需要综合测试来检验。但是综合测试必须要有足量的测试集合才能达到效果。因此为了进行测试,四个单元都设计了全自动的对拍器来进行测试。对于第二单元的对拍器测试主要测试电梯是否能够正确的送客,对于剩下三个单元的对拍器重点主要在于数据的生成。尤其在第三单元数据的结构会影响到测试的全面性。因此对拍器设计过程中数据生成要重点考虑,需要设计多种数据生成模式才能达到完整的测试。

课程收获

  • 这一学期的面向对象课程,总的来说收获了很多。
  • 首先最重要的一点是学会了Java编程,并且在普通编程的基础上学会了面向对象编程思想,学会了继承,封装,多态的编程模式。
  • 其次在第二单元学会了多线程编程,理解了多线程中处理线程安全性的一些通用做法
  • 认识并理解了规格设计,同时结合面向对象思想,对于面向对象编程有了更深一步的认识
  • 认识并理解了模型设计,了解了UML建模,认识了先建模后编程的思想
  • 此外,还进一步学会了新的测试模式和测试方式。认识并学会了单元测试,清楚的知道如何在没有完全完成程序的情况下对方法的正确性进行测试。此外也额外实践了如何编写具有针对性的对拍器,完成自动化测试。

建议

  • 经过一个学期的面向对象课程学习,觉得课程有以下几点可改进之处:
  • 建议在评测网页新添一个提交数据评测功能,即能够提交自己出的测试数据对自己提交的代码进行评测(防止滥用可以设置前后提交数据等待时间),以便我们能够清楚题目需求
  • 建议能够改进实验课时间安排,最好能错开不要同一天,不然早上刚讲的知识中午还没消化完下午就考试显得有些仓促
posted @ 2019-06-22 20:56  PX_L  阅读(228)  评论(0编辑  收藏  举报