【问题标题】:Encapsulation between view and controller视图和控制器之间的封装
【发布时间】:2015-07-15 10:50:51
【问题描述】:

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

封装视图和控制器之间传递的数据

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

我会试着用一个例子来解释这个问题:

用户想要查看所有项目。因此,他将启动应用程序并单击标有“显示项目”的按钮。该按钮将在控制器中启动一个名为 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);
}
}

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

我的问题是:“有没有更简单的方法来保持视图和控制器之间的封装?”

【问题讨论】:

    标签: java design-patterns model-view-controller


    【解决方案1】:

    复制所有这些类感觉不太对

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

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

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

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

    DTO 是简单的对象,不应包含任何需要测试的业务逻辑

    【讨论】:

    • 谢谢,尽管valueObject 说 DTO ~= Value Object
    • 从值对象到 DTO 的引用以一种令人困惑的方式编写,但它的意思是人们通常将它们谈论为 bean 或 pojos。它接着说,“......[这些] 都不能严格地被视为一个值对象。”
    猜你喜欢
    • 2014-04-10
    • 1970-01-01
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-10
    • 1970-01-01
    • 2016-06-10
    相关资源
    最近更新 更多