java - View 和 Controller 之间的封装

标签 java design-patterns model-view-controller

我正在开展一个学校项目,我们需要创建一个管理任务和项目的基本应用程序。我们的项目遵循 MVC 模式。重点在于应用程序的设计,我们的团队一直在努力解决某个设计问题:

封装 View 和 Controller 之间传递的数据

这意味着我们要确保 View 没有任何对真实数据的引用。我们尝试通过创建值类来解决这个问题,但这是一个巨大的解决方法。这些是最终类,本质上是普通模型类的副本。例如,如果您有一个 Project 类,那么您还将有一个名为 ProjectValue 的最终类,其所有字段与 Project 相同,只是它们是allfinal 使值类的对象不可变,因此 View 中没有任何内容可以更改。复制所有这些类来获得一些额外的封装感觉不太合适,必须有一种更简单的方法。

我将尝试用一个例子来解释这个问题:

用户想要查看所有项目。因此,他将启动应用程序并单击标有“显示项目”的按钮。该按钮将在 Controller 中启动一个名为 getAll() 的方法:

public PList<ProjectValue> getAll()
{
    PList<ProjectValue> projects = PList.empty();

    for (BranchOffice office : company.getBranchOffices())
    {
        for (Project project : office.getProjects())
        {
            projects = projects.plus(project.getValue());
        }
    }

    return projects;
}

首先它循环遍历所有分支机构。对于分支机构中的每个项目,它都会获取项目的值对象 (project.getValue()) 并将其放入列表中,而不是普通项目中。

模型类及其内部值类的示例:

public class Resource implements Serializable, Comparable<Resource> {

/**
 * Variable registering the name for this resource.
 */
private String            name;

private BranchOffice office;

/**
 * Variable registering the resource type for this resource.
 */
private ResourceType      type;

/**
 * Variable registering the reservations that reserve this resource.
 */
private Set<Reservation> reservations;


/**
 * Initializes the resource with a given name and type.
 * 
 * @param  name
 *         The name for the resource, f.e. Audi
 * @param  type
 *         The type of the resource, f.e. Car
 * @throws InvalidResourceException 
 */
public Resource(String name, BranchOffice office,  ResourceType type)
        throws InvalidResourceException
{
    try
    {
        setName(name);
        setBranchOffice(office);
        setType(type);
        setReservations(null);
    } catch (InvalidRequiredStringException
            | InvalidRequiredResourceTypeException e)
    {
        throw new InvalidResourceException(e.getMessage(), this);
    }
}

/**
 * @return the key
 */
public String getKey() { return name; }
/**
 * @return the type
 */
private ResourceType getType() { return type; }
private String getName() { return name; }
public Set<Reservation> getReservations() { return reservations; }

public BranchOffice getBranchOffice()
{
    return office;
}

/**
 * @param name the name to set
 * @throws InvalidRequiredStringException 
 */
private void setName(String name) throws InvalidRequiredStringException
{
    if (name != null && !name.trim().isEmpty())
        this.name = name;
    else
        throw new InvalidRequiredStringException(INVALID_NAME, name);
}

private void setBranchOffice(BranchOffice office)
{
    if (office == null) {
        throw new IllegalArgumentException(INVALID_OFFICE);
    } else {
        this.office = office;
    }

}

/**
 * @param type the type to set
 * @throws InvalidRequiredResourceTypeException 
 */
private void setType(ResourceType type)
        throws InvalidRequiredResourceTypeException
{
    if (type == null)
        throw new InvalidRequiredResourceTypeException(INVALID_TYPE, type);
    else
        this.type = type;
}

/**
 * Set the list of reservations to a given list.
 * 
 * @param reservations
 *        | The list you want to set the reservations to.
 */
private void setReservations(Set<Reservation> reservations)
{
    if (reservations != null) this.reservations = new HashSet<>(reservations);
    else this.reservations = new HashSet<>();
}

/**
 * Adds a given reservation to the list of reservations.
 * 
 * @param reservation
 *        | The reservation you want to add  to the reservations.
 */
private void addReservation(Reservation reservation)
{
    this.reservations.add(reservation);
}

/**
 * Checks if this resource conflicts with a given resource.
 * 
 * @param resource
 *        The resource you want to check against.
 * @return
 *        True if this resource conflicts with the given resource.
 */
public boolean conflictsWith(Resource resource)
{
    if (getType().hasConflictWith(resource.getType())) return true;
    else return false;
}

/**
 * Checks if a resource if available for a given timespan
 * 
 * @param  timespan
 * @return True if the timespans do not overlap.
 */
public boolean isAvailable(TimeSpan timespan)
{
    if (reservations != null && !reservations.isEmpty())
    {
        for (Reservation reservation : reservations)
            if (reservation.overlapsWith(timespan))
                return false;
        // TODO: checken of resource beschikbaar is binnen timespan (bv.
        // datacenter enkel beschikbaar tussen 12:00 en 17:00
        return true;
    }
    return true;
}


@Override
public int hashCode()
{
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Resource other = (Resource) obj;
    if (name == null)
    {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}

public ResourceValue getValue()
{
    return new ResourceValue(this);
}

@Override
public int compareTo(Resource o)
{
    return this.getKey().compareTo(o.getKey());
}

public boolean isOfType(ResourceType other)
{
    return getType().equals(other);
}

public void reserve(Reservation newReservation) throws InvalidReservationException
{
    for(Reservation reservation : getReservations())
        if(reservation.conflictsWith(newReservation)) 
            throw new InvalidReservationException("Reservation conflicts with another reservation", newReservation);
    addReservation(newReservation);
}

public boolean isOfSameType(Resource resource)
{
    return isOfType(resource.getType());
}

public class ResourceValue
{
    private final String name;
    private final ResourceType type;

    private ResourceValue(Resource resource)
    {
        this.name = resource.getName();
        this.type = resource.getType();
    }
    /**
     * @return the name
     */
    public String getName() { return name; }
    /**
     * @return the type
     */
    public ResourceType getType() { return type; }
}

public void deleteReservation(Reservation reservation)
{
    getReservations().remove(reservation);
}
}

我已经复制了整个类,它看起来有点乱,但尝试查看类的底部,在那里你可以找到值类。我选择这门课是因为它是最小的一门课。在此示例中,值类不会复制所有字段,而仅复制 View 所需的字段。

我的问题是:“有没有更简单的方法来保持 View 和 Controller 之间的封装?”

最佳答案

It just doesn't feel right to kind of duplicate all these classes

当您将应用程序拆分为多个层时,最好使用这种“ObjectValue”也称为“ObjectDto,数据传输对象”[1]。过去没有模型对象的纯粹副本,您可以根据您想要执行的操作添加、删除、修改字段。

也就是说,您可以使用一些库将实体“映射”到 ObjectValue。例如,ModelMapper http://modelmapper.org/ .

PersonDto personDto = mapper.map(personModel, PersonDto.class);

编辑: [1] 根据评论,ValueObject 和 DTO 不是同一件事,即使主要原理保持不变。 IMO,这只是命名约定的问题。

DTOs are simple objects that should not contain any business logic that would require testing

关于java - View 和 Controller 之间的封装,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31428352/

相关文章:

javascript - AngularJS 用函数调用数组

model-view-controller - 存储库模式 : What is the 'right size' ?

java - Hibernate 3.5.1、JPA2.0 和 MySQL - 2 个差异数据库/同一服务器内的替代行为

java - 将 JComponent 的宽度和高度设置为实例变量

Java:装饰器模式 - 对主要抽象类的引用

silverlight - PRISM 是否适用于大规模应用程序开发?

java - Ajax 看不到 Post Controller

java - 如果我们使用 Collections.synchronizedList(Collection c) 同步遍历 block 的目的是什么?

java - 如何禁用 Jackson 从 epoch millis 反序列化 Instant?

c# - REST API 响应