首页 数据库 mysql教程 论基于数据访问的集合类(Data Access Based Collection)和领域

论基于数据访问的集合类(Data Access Based Collection)和领域

Jun 07, 2016 pm 03:37 PM
access data 基于 数据 访问 集合

在正式展开之前,有一些概念要先做一个界定。首先: 领域 模型是指系统应对的 领域 中所有逻辑的一个抽象,本质上它是 领域 中各种对象和概念以及它们之间关系的 集合 。你可以用自然语言描述它,也可以用UML来描述,或者是代码去描述。特别地,当我们使用面

        在正式展开之前,有一些概念要先做一个界定。首先:领域模型是指系统应对的领域中所有逻辑的一个抽象,本质上它是领域中各种对象和概念以及它们之间关系的集合。你可以用自然语言描述它,也可以用UML来描述,或者是代码去描述。特别地,当我们使用面向对象建模技术来实现这个领域模型时,我们可以把这个实现出来的模型称之为对象模型。我们可以认为领域模型是一个概念模型,是分析阶段的产物。

  让精心构建的对象模型高效地工作有很多底层的技术问题需要解决,其中如何满足领域对象的业务方法在计算过程中对数据的需求是一个普遍存在的问题(实际上,在实际应用中,我们会遇到更为复杂的情况,不只是有数据的需求,还可能出现对应用层面发生依赖)。对于这一问题,目前有两种模型可供借鉴,那就是基于数据访问集合类和领域事件模式


基于数据访问集合类(Data Access Based Collection)


  基于数据访问集合类是我在开发oobbs系统时设计的一种模式。这一模式通过一个抽象的接口来代表某一对象依赖的一组集合。当这一对象实例化时,一个基于数据访问集合实现类会注入到这个对象中,所有通过这一集合进行的操作,比如遍历,增删元素等都是被实现类转化成数据访问操作。基于数据访问集合类很像是一个缩水版的Repository。还是以Forum的public List getThreads()方法为例,我们认为getThreads是Forum的一个典型的业务方法,但是由于一个Forum拥有众多的Thread,这使得我们根本不容许一次将这个集合全部加载出来。即使是在hibernate这类提供了lazy和extra lazy加载机制ORM工具里,也无法避免当我们直得去迭代这一集合时,它们会被一次性全部加载。而另一方面,实际的应用请求也不会一次请求所有的Thread,更常见的情况是以分页的形式,一小批次一小批次地请求。因此基于数据访问集合模式使用一个集合接口做为一个占位符,并声明了一些基本的集合操作:比如返回某一区间内的子集(为分页而服务)和add,remove等操作,而实现类里,这些方法是以数据访问的方式实现的。下面是集合的接口定义。它看起来很像一个普通的集合
package oobbs.domainmodel;

import java.io.Serializable;
import java.util.List;

/**
 * The collection interface represents a set of objects, it's like the
 * java.util.Collection, however, there no real objects in this collection, it
 * only looks like a collection, its method's implementation is database access
 * operation! see <code>oobbs.infrastructure.persistence.AbstractHibernateCollection</code> 
 * @author laurence.geng
 */
public interface Collection<entity pk extends serializable owner> {
	void setOwner(Owner owner);

	void setOwnerName(String ownerName);

	/**
	 * Adds an object. This method will persist entity to database directly!
	 * @param e an entity instance. * @return the pK the generated primary key
	 * after insert into database.
	 */
	PK add(Entity e);

	void addAll(java.util.Collection<entity> c);

	/**
	 * Removes the entity. This method will remove this entity from database
	 * directly.
	 */
	void remove(Entity e);

	void removeAll(java.util.Collection<entity> c);

	boolean contains(Entity o);

	boolean isEmpty();

	int size();

	/**
	 * The most important method. It returns a subset of the whole collection.
	 * the returned subset is fetched from database by sql, hql or other data
	 * access way, The Collection itself never load all elements once time!
	 */
	List<entity> toList(int startIndex, int offset);

	void flush();
}</entity></entity></entity></entity>
登录后复制

  下面则是基本于hibernate的集合接口实现类。它实现了所有的基本的操作。在Forum类中就会这样一个字段以及相应的getter和setter:

	@Transient
	private Collection<thread long forum> forumThreads;

	@Autowired
	/**
	 * Sets ForumThreadCollection. 
	 * ForumThreadCollection is injected by this setter. When a collection instance injected, set this forum to its forum! 
	 */
	public void setForumThreads(@Qualifier("forumThreads") Collection<thread long forum> forumThreads) {
		this.forumThreads = forumThreads;
		this.forumThreads.setOwner(this);
		this.forumThreads.setOwnerName("forum");
	}

	/**
	 *  Gets this forum's thread collection.
	 */
	public Collection<thread long forum> getForumThreads() {
		return forumThreads;
	}</thread></thread></thread>
登录后复制

  其中注入的forumThreads对象是一个名为ForumThreadHibernateCollection的类,它继承了AbstractHibernateCollection类,因为没有特殊的需要,没有重写任何方法。而下面展示的是service中对这集合的一次使用:
List<thread> threads = forum.getForumThreads().toList(startThreadIndex,threadTotal);</thread>
登录后复制

  我们来分析一下Domain Collection这一模式的优劣。我认为它最大的优点在于它能够以一个字段的形式存在于单端关联对象中,这使得单端对象的定义饱满,完成符合并体现了一对多双向关联中双方依赖关系。这一点是使用hiberate映射无法实现的,因为我们不能在Forum中映射@OneToMany(mappedBy="forum") private Set threads;

  但是它的缺点也是非常明显并且似乎是无法克服的,那就是它只能用来表示直接关联的集合,如果单端对象想通过这一集合进一步遍历元素中更深层次的二级,三级集合时,domain collection就显得力不从心了。比方说:在论坛的首页上往往会罗列出各个Forum的一些基本信息,其中之一就是该Forum有多少帖子(Post),相应的,Forum对象会有这样一个方法public Long getPostCount();很显然,Post是Forum的二级集合,一个Forum需要先得到它的Thread集合,再从每个Thread中得到Post集合。我们可以为了这一方法再提供一个ForumPostHibernateCollcetion用来代表一个Forum的所有Post的集合,但是这个集合已经和模型的定义发生了偏离,因为并没有从Forum到Post的直接关联。而更加普遍的问题的是:我们会常常遇到某一个单端实体从它直接依赖的对象开始进行深度地导航(体现在SQL上就是对多个表的join操作),这时候我们不能为每一种导航而创建一个从出发点到结束点的domain collection。在这种情况下,获取数据必须通过另外一种方式进行了,那就是Domain Event模式


领域事件(Domain Event)

  Domain Event模式最初由udi dahan提出,发表在自己的博客上:http://www.udidahan.com/2009/06/14/domain-events-salvation/这一模式得到广泛的认可。它所要应对的正是将领域对象从对repository或service的依赖中解脱出来,避免让领域对象对这些设施产生直接依赖。它的做法就是当领域对象的业务方法需要依赖到这些对象时就发出一个事件,这个事件会被相应的对象监听到并做出处理。在我的oobbs系统中,我对这一模式做了一些改进,主要是消除静态方法和规范事件模型。在我的方案中,这一机制由这样几个角色:DomainEventDispatcher,DomainEvent和DomainEventListener.

  DomainEventDispatcher会以字段的形式存在于领域对象中,负责在业务方法中dispatch领域事件。下面是所有Dispatcher的基类:

package oobbs.domainmodel;

import java.util.HashMap;
import java.util.Map;

/**
 * The DomainEventDispatcher take charge of listener registration and dispatch
 * domain event to corresponding listener to handle. Usually, one dispatch per
 * domain object.
 */
public class DomainEventDispatcher {
	/** The listener map. all registered listeners are stored in this map. */
	protected Map<string domainobejctlistener> listeners = new HashMap<string domainobejctlistener>();

	/** * Adds a listener. * * @param listener the listener */
	public void addListener(DomainObejctListener listener) {
		listeners.put(listener.getName(), listener);
	}

	/** * Removes all listeners. */
	public void removeAllListeners() {
		listeners.clear();
	}
}</string></string>
登录后复制

一般来说一个领域对象会有一个对应的event dispatcher,这个dispatcher会有一组重载的dispatch方法,用于分发不同的领域事件。下面是oobbs中Forum对象的event dispatcher.

package oobbs.domainmodel.forum;

import oobbs.Constants;
import oobbs.domainmodel.DomainEventDispatcher;
import oobbs.domainmodel.ResultCollector;

/**
 * * The ForumEventDispatcher dispatch all events which about Forum object. * @author
 * laurence.geng
 */
public class ForumEventDispatcher extends DomainEventDispatcher {
	public void dispatch(GetForumThreadEvent event, ResultCollector result) {
		ForumListener forumListener = (ForumListener) listeners.get(Constants.FORUM_REPO_AS_FORUM_LISTENER);
		forumListener.handleGetForumThreadEvent(event, result);
	}

	public void dispatch(GetForumPostCountEvent event, ResultCollector result) {
		ForumListener forumListener = (ForumListener) listeners.get(Constants.FORUM_REPO_AS_FORUM_LISTENER);
		forumListener.handleGetFroumPostCountEvent(event, result);
	}

	public void dispatch(GetForumThreadCountEvent event, ResultCollector result) {
		ForumListener forumListener = (ForumListener) listeners.get(Constants.FORUM_REPO_AS_FORUM_LISTENER);
		forumListener.handleGetForumThreadCountEvent(event, result);
	}
}
登录后复制

系统中会有很多的domain event,下面是所有领域事件的基类:
package oobbs.domainmodel;

/**
 * The supper class of all domain events. all events should provide event
 * source, the domain object which fired this event.
 */
public class DomainEvent {
	/** The event source, the domain object which fired this event. */
	protected Object source;

	public DomainEvent(Object source) {
		super();
		this.source = source;
	}

	/** * Gets the event source. * * @return the event source */
	public Object getSource() {
		return source;
	}
}
登录后复制

下面就是刚才提到的例子中返回Forum某一部分(分页)Thread的事件,在这个事件中我们看到一个事件可以携带一些参数,供listener使用:
package oobbs.domainmodel.forum;

import oobbs.domainmodel.DomainEvent;

/** 
 * The Event that forum request to get its threads.
 * @author laurence.geng 
 */
public class GetForumThreadEvent extends DomainEvent {
	/** The start index of request thread. */
	private int startIndex;
	/** The count of request thread. */
	private int count;

	public GetForumThreadEvent(Forum source, int startIndex, int count) {
		super(source);
		this.startIndex = startIndex;
		this.count = count;
	}

	public int getStartIndex() {
		return startIndex;
	}

	public int getCount() {
		return count;
	}
}
登录后复制

而下面就是我们所有listener的基类:
package oobbs.domainmodel;

/**
 * The super class of all domain object listeners. it handles events from
 * domain objects. Each listener has to provide a name as key for registering
 * itself to dispatcher. 
 * @see DomainObejctEvent 
 * @author laurence.geng
 */
public interface DomainObejctListener {
	/** * Gets the name. * * @return the name */
	public String getName();
}
登录后复制

下面是Forum的listenerr接口,这个接口会有很多handle方法,代码只展示了一个。
package oobbs.domainmodel.forum;

import oobbs.domainmodel.DomainObejctListener;
import oobbs.domainmodel.ResultCollector;

/**
 * * The forum listener. It handles all events from Forum object. * * @see
 * ForumEvent * @author laurence.geng
 */
public interface ForumListener extends DomainObejctListener {
	/**
	 * * Handle the event that a forum requests to get its threads. * * @param
	 * event the event * @param result the result
	 */
	public void handleGetForumThreadEvent(GetForumThreadEvent event,ResultCollector result);
}
登录后复制

然后 是这个接口一个实现类,在oobbs中,做为forum的repository的实现类:ForumHibernateRepository,自然成为实现这一接口的最佳选择:
/**
 * * The Forum's repository with hibernate implementation, besides, it's a forum
 * listener which handle * all events come from forum object.
 */
public class ForumHibernateRepository extends AbstractHibernateRepository<forum long> implements ForumRepository {
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * oobbs.domainmodel.forum.ForumListener#handleGetForumThreadEvent(oobbs
	 * .domainmodel.forum.GetForumThreadEvent,
	 * oobbs.domainmodel.ResultCollector)
	 */
	@Overridepublic
	void handleGetForumThreadEvent(final GetForumThreadEvent event, ResultCollector result) {
		List<thread> threads = (List<thread>) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
					@SuppressWarnings("unchecked")
					public Object doInHibernate(Session session) throws HibernateException, SQLException {
						logger.info("Start to load threads of forum.");
						// Get forum.
						String getForuumThreadHql = "from Thread as thread where thread.forum=:forum";
						List<thread> threads = (List<thread>) session
								.createQuery(getForuumThreadHql)
								.setCacheable(true)
								.setParameter("forum", event.getSource())
								.setFirstResult(event.getStartIndex())
								.setMaxResults(event.getCount()).list();
						return threads;
					}
				});
		result.add(threads);
	}
}
</thread></thread></thread></thread></forum>
登录后复制

最后我们看一看这一切是如被触发的。我们来看forum的这个方法:oobbs.domainmodel.forum.Forum.getThreads(int, int):
	public List<thread> getThreads(int startIndex, int count) {
		GetForumThreadEvent event = new GetForumThreadEvent(this, startIndex, count);
		ResultCollector result = new ResultCollector();
		forumEventDispatcher.dispatch(event, result);
		return (List<thread>) result.getUniqueResult();
	}</thread></thread>
登录后复制

  很简洁的四行代码:分别new一个event和result collector,然后用ForumEventDispatcher来dispatch这个事件,然后收集返回的处理结果。上述所有组件都是围绕这里而设计的,我们希望在领域对象的业务方法里不会出现任何repository或service,DomainEvent模式很好的满足了我们的需求,并且是以一种非常优雅而简洁的方式。

       当然,在上面讲述的整个机制中,我们漏掉了一环没有讲,那就是dispatcher是如何被实例化并完成注册listener工作的。由于oobbs使用了spring的IOC管理对象, dispatcher是由IOC创建并完成注册listener等初始化工作的。下面的类完成了这一系列工作:

package oobbs.infrastructure.appcontext;

import oobbs.domainmodel.DomainObejctListener;
import oobbs.domainmodel.forum.ForumEventDispatcher;
import oobbs.domainmodel.forum.ThreadEventDispatcher;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * * The ApplicationBeanPostProcessor will do some initialization work when bean
 * is created by spring ioc container, * such as: Adding listeners for domain
 * event dispatchers and so on. * @author laurence.geng
 */
public class ApplicationBeanPostProcessor implements BeanPostProcessor,
		ApplicationContextAware {
	/** The Constant logger. */
	private static final Logger logger = Logger
			.getLogger(ApplicationBeanPostProcessor.class);
	/** The application context. */
	private ApplicationContext applicationContext;

	/*
	 * (non-Javadoc) * @see org.springframework .beans.factory
	 * .config.BeanPostProcessor #postProcessBeforeInitialization
	 * (java.lang.Object, java.lang.String)
	 */

	public Object postProcessBeforeInitialization(Object bean, String beanName)	throws BeansException {
		return bean; // we could potentially return any object reference here...
	}

	/*
	 * (non-Javadoc) * @see
	 * org.springframework.beans.factory.config.BeanPostProcessor
	 * #postProcessAfterInitialization(java.lang.Object, java.lang.String)
	 */
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		logger.debug("Bean '" + beanName + "' created : " + bean.toString());
		// Adding listeners for domain event dispatchers.
		if ("forumEventDispatcher".equals(beanName)) {
			ForumEventDispatcher forumEventDispatcher = (ForumEventDispatcher) bean;
			forumEventDispatcher.addListener((DomainObejctListener) applicationContext.getBean("forumRepository"));
		}
		if ("threadEventDispatcher".equals(beanName)) {
			ThreadEventDispatcher threadEventDispatcher = (ThreadEventDispatcher) bean;
			threadEventDispatcher.addListener((DomainObejctListener) applicationContext.getBean("threadRepository"));
		}
		return bean;
	}

	/*
	 * (non-Javadoc) * @see
	 * org.springframework.context.ApplicationContextAware#setApplicationContext
	 * (org.springframework.context.ApplicationContext)
	 */
	@Override
	public void setApplicationContext(ApplicationContext arg0) throws BeansException {
		this.applicationContext = arg0;
	}
}
登录后复制


小结

  最后总结一下:其实基于数据访问集合类(Data Access Based Collection)和领域事件(Domain Event)两者各有优劣。在实际中可以结合使用。如果只使用数据访问集合类,则很难支持二、三级关联,而一味地使用领域事件则有会导致引入过多的事件类,引起类型爆炸。
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Windows11怎么禁用后台应用程序_Windows11禁用后台应用教程 Windows11怎么禁用后台应用程序_Windows11禁用后台应用教程 May 07, 2024 pm 04:20 PM

1、在Windows11中打开设置。您可以使用Win+I快捷方式或任何其他方法。2、转到应用程序部分,然后单击应用程序和功能。3、查找要阻止在后台运行的应用程序。单击三点按钮并选择高级选项。4、找到【后台应用程序权限】部分并选择所需的值。默认情况下,Windows11设置电源优化模式。它允许Windows管理应用程序在后台的工作方式。例如,一旦启用省电模式以保留电池,系统将自动关闭所有应用程序。5、选择【从不】可防止应用程序在后台运行。请注意,如果您注意到程序不向您发送通知、无法更新数据等,您可

iPhone上的蜂窝数据互联网速度慢:修复 iPhone上的蜂窝数据互联网速度慢:修复 May 03, 2024 pm 09:01 PM

在iPhone上面临滞后,缓慢的移动数据连接?通常,手机上蜂窝互联网的强度取决于几个因素,例如区域、蜂窝网络类型、漫游类型等。您可以采取一些措施来获得更快、更可靠的蜂窝互联网连接。修复1–强制重启iPhone有时,强制重启设备只会重置许多内容,包括蜂窝网络连接。步骤1–只需按一次音量调高键并松开即可。接下来,按降低音量键并再次释放它。步骤2–该过程的下一部分是按住右侧的按钮。让iPhone完成重启。启用蜂窝数据并检查网络速度。再次检查修复2–更改数据模式虽然5G提供了更好的网络速度,但在信号较弱

超级智能体生命力觉醒!可自我更新的AI来了,妈妈再也不用担心数据瓶颈难题 超级智能体生命力觉醒!可自我更新的AI来了,妈妈再也不用担心数据瓶颈难题 Apr 29, 2024 pm 06:55 PM

哭死啊,全球狂炼大模型,一互联网的数据不够用,根本不够用。训练模型搞得跟《饥饿游戏》似的,全球AI研究者,都在苦恼怎么才能喂饱这群数据大胃王。尤其在多模态任务中,这一问题尤为突出。一筹莫展之际,来自人大系的初创团队,用自家的新模型,率先在国内把“模型生成数据自己喂自己”变成了现实。而且还是理解侧和生成侧双管齐下,两侧都能生成高质量、多模态的新数据,对模型本身进行数据反哺。模型是啥?中关村论坛上刚刚露面的多模态大模型Awaker1.0。团队是谁?智子引擎。由人大高瓴人工智能学院博士生高一钊创立,高

美国空军高调展示首个AI战斗机!部长亲自试驾全程未干预,10万行代码试飞21次 美国空军高调展示首个AI战斗机!部长亲自试驾全程未干预,10万行代码试飞21次 May 07, 2024 pm 05:00 PM

最近,军事圈被这个消息刷屏了:美军的战斗机,已经能由AI完成全自动空战了。是的,就在最近,美军的AI战斗机首次公开,揭开了神秘面纱。这架战斗机的全名是可变稳定性飞行模拟器测试飞机(VISTA),由美空军部长亲自搭乘,模拟了一对一的空战。5月2日,美国空军部长FrankKendall在Edwards空军基地驾驶X-62AVISTA升空注意,在一小时的飞行中,所有飞行动作都由AI自主完成!Kendall表示——在过去的几十年中,我们一直在思考自主空对空作战的无限潜力,但它始终显得遥不可及。然而如今,

deepseek怎么转换pdf deepseek怎么转换pdf Feb 19, 2025 pm 05:24 PM

DeepSeek 无法直接将文件转换为 PDF。根据文件类型,可以使用不同方法:常见文档(Word、Excel、PowerPoint):使用微软 Office、LibreOffice 等软件导出为 PDF。图片:使用图片查看器或图像处理软件保存为 PDF。网页:使用浏览器“打印成 PDF”功能或专用的网页转 PDF 工具。不常见格式:找到合适的转换器,将其转换为 PDF。选择合适的工具并根据实际情况制定方案至关重要。

特斯拉机器人进厂打工,马斯克:手的自由度今年将达到22个! 特斯拉机器人进厂打工,马斯克:手的自由度今年将达到22个! May 06, 2024 pm 04:13 PM

特斯拉机器人Optimus最新视频出炉,已经可以在厂子里打工了。正常速度下,它分拣电池(特斯拉的4680电池)是这样的:官方还放出了20倍速下的样子——在小小的“工位”上,拣啊拣啊拣:这次放出的视频亮点之一在于Optimus在厂子里完成这项工作,是完全自主的,全程没有人为的干预。并且在Optimus的视角之下,它还可以把放歪了的电池重新捡起来放置,主打一个自动纠错:对于Optimus的手,英伟达科学家JimFan给出了高度的评价:Optimus的手是全球五指机器人里最灵巧的之一。它的手不仅有触觉

单卡跑Llama 70B快过双卡,微软硬生生把FP6搞到了A100里 | 开源 单卡跑Llama 70B快过双卡,微软硬生生把FP6搞到了A100里 | 开源 Apr 29, 2024 pm 04:55 PM

FP8和更低的浮点数量化精度,不再是H100的“专利”了!老黄想让大家用INT8/INT4,微软DeepSpeed团队在没有英伟达官方支持的条件下,硬生生在A100上跑起FP6。测试结果表明,新方法TC-FPx在A100上的FP6量化,速度接近甚至偶尔超过INT4,而且拥有比后者更高的精度。在此基础之上,还有端到端的大模型支持,目前已经开源并集成到了DeepSpeed等深度学习推理框架中。这一成果对大模型的加速效果也是立竿见影——在这种框架下用单卡跑Llama,吞吐量比双卡还要高2.65倍。一名

Java反射机制如何修改类的行为? Java反射机制如何修改类的行为? May 03, 2024 pm 06:15 PM

Java反射机制允许程序动态修改类的行为,无需修改源代码。通过Class对象操作类,可以通过newInstance()创建实例,修改私有字段值,调用私有方法等。但应谨慎使用反射,因为它可能会导致意外的行为和安全问题,并有性能开销。

See all articles