技术私语
分页: 1/5 第一页 1 2 3 4 5 下页 最后页 [ 显示模式: 摘要 | 列表 ]

让ibatis插上c3p0的翅膀

[不指定 2008/11/05 01:29 | by edwardproAdmin ]

ibatis真是个不错的东西,它特别合适小型的数据库应用使用,不过它的连接池比较简单,估计很难适应真正的生产环境,得改改,其实之前网上也看到别人写的,但是我不是很明白意图...

其实这个ds只需要继承一个接口,首先来看看接口是什么样子的:

package com.ibatis.sqlmap.engine.datasource;

import javax.sql.DataSource;
import java.util.Map;

/**
 * Interface to provide a way to create and configure a DataSource for iBATIS
 */
public interface DataSourceFactory {

  /**
   * Simple method to initialize/configure a datasource
   *
   * @param map - the configuration information
   */
  public void initialize(Map map);

  /**
   * Returns a datasource
   *
   * @return an implementation of DataSource
   */
  public DataSource getDataSource();

}

目的是很明确,只需要解决初始化和get两个方法,第一个是真正的参数初始化方法,后一个是系统得到ds对象的方法,目标明确了,这样下一步操作就简单了,看我写的:

public class C3p0DataSource implements DataSourceFactory {
 /**
  * Logger for this class
  */
 private static final Logger logger = Logger.getLogger(C3p0DataSource.class);

 private DataSource ds;

 private static final String DB_URL = "JDBC.ConnectionURL";

 private static final String DB_USER = "JDBC.Username";

 private static final String DB_PASS = "JDBC.Password";

 private static final String DB_DRIVER = "JDBC.Driver";

 private String dbUrl;

 private String dbUser;

 private String dbPass;

 private String dbDriver;

 public C3p0DataSource() {
  logger.info("start for c3p0 ds");
 }

 @Override
 public DataSource getDataSource() {
  // TODO Auto-generated method stub
  return this.ds;
 }

 @Override
 public void initialize(Map map) {
  // TODO Auto-generated method stub
  this.setDbUrl(String.valueOf(map.get(C3p0DataSource.DB_URL)));
  this.setDbUser(String.valueOf(map.get(C3p0DataSource.DB_USER)));
  this.setDbPass(String.valueOf(map.get(C3p0DataSource.DB_PASS)));
  this.setDbDriver(String.valueOf(map.get(C3p0DataSource.DB_DRIVER)));
  try {
   Class.forName(this.getDbDriver());
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

  try {
   this.ds = DataSources.unpooledDataSource(this.getDbUrl(), this
     .getDbUser(), this.getDbPass());
   this.ds = DataSources.pooledDataSource(this.ds);
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

}

测试已经能正常工作了^^准备休息,累了...

lucene 2.4的变化

[不指定 2008/10/22 08:16 | by edwardproAdmin ]
今天乘机升级了所有的lucene平台,很顺利没有遇到太多问题.但lucene确实有些变化,至少我能看到的就是, 他把hits这个类给杀掉了,hits这个类以前也说过,可以说他就是缓存结果的最大障碍,但这个问题不难解决,只要记住:一切query都是filter就可以了.不过如今lucene废掉了hits这个类和他连带的方法,今后query只有topdocs了,这样的变化让我引起一些兴趣,稍稍研究后,再做汇报.

memcached的性能极限

[不指定 2008/10/10 07:40 | by edwardproAdmin ]
最近测试memcached的并发查询效率(并非连接效率),基本上在每秒100次,超过之后性能下降非常严重.测试中当查询超过100之后,memcached进程的cpu占用率可达到平均35%(8线程cpu)左右,并且速度效率下降非常明显,因此在memcached使用需要注意:
1 控制socket连接数,连接数大量增加会导致机器负荷上升,因此建议使用连接池,我测试使用的连接池最大连接数才15
2 控制缓存使用颗粒度尺寸,不适宜过小的颗粒度,基本保持在30次/s查询比较好
一直觉得lucene的reopen有问题,今天特地研究了下,顺便熟悉了整个IndexReader的结构体系

IndexReader是一个体系,他是search的核心io之一

rg.apache.lucene.index
Class IndexReader

java.lang.Object   org.apache.lucene.index.IndexReader 
Direct Known Subclasses:
FilterIndexReader, MultiReader, ParallelReader

先来看看IndexReader下面的类体系我们平常会使用的三个派生: FilterIndexReader MultiReader ParallelReader


下面来看看我犯错误的reopen:
public IndexReader reopen()                    throws CorruptIndexException,                           IOException
之前为什么我错了,我以为这个方法是自省的(根本没有注意它的返回不是void的道理么...),所以我怎么写的?

iSearch.getIndexReader().reopen();

误以为这样就可以reopen,结果总是被投诉没有更新,所以一直觉得更新有问题,实际上是我用法的问题,应该是这样的.

if (iSearch != null) {
// 新修改的reopen方法
try {
IndexReader irOld = this.reOpen(iSearch);
// isearchMap.remove(searchBean.getIsearchName());
isearchMap.put(searchBean.getIsearchName(),
new IndexSearcher(irOld));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 注意要关闭掉原来的iSearch
iSearch.close();
iSearch = null;
logger.info(">>>>>>>>>>>>>one search has been reopen:"
+ searchBean.getIsearchName());
}
}

上面是从程序里截取的看看意思就好了.

但后来发现这样还是不对的,看源码上的说明:
If the index has not changed since this instance was (re)opened, then this
* call is a NOOP and returns this instance. Otherwise, a new instance is
* returned. The old instance is <b>not</b> closed and remains usable.

如果没有改变的话,他会返回null的,这样不得不判断一下,所以代码要这样:

if (iSearch != null) {
// 新修改的reopen方法
try {
IndexReader irOld = reOpen(iSearch);
if (irOld != null) {
// isearchMap.remove(searchBean.getIsearchName());
isearchMap.put(searchBean.getIsearchName(),
new IndexSearcher(irOld));
// 注意要关闭掉原来的iSearch
iSearch.close();
iSearch = null;
}
} catch (Exception e) {
logger.error(e);
} finally {
logger.info(">>>>>>>>>>>>>one search has been reopen:"
+ searchBean.getIsearchName());
}
}

这样可以在索引不变下不产生更多io消耗,那么我们可以这样理解reopn的实质:

首先判断 lastmodify时间,如果被更新了则, new 一个IndexReader, 否则就直接返回null,而操作方法都扔给了用户. 所以官方所谓的reopen能减少消耗说法并不准确,它做的只是在改变时重新连接,但这个他官方文档所说的只更新更新部分有很大不同,现在只是选择性地降低了io,但是如果每次探测时索引都变了就变得没有意义,这样的好吃是,索引的跟踪可以变得更频繁,也不再需要程序过多干预了.

但是目前这个方法写得不够成熟,至少我认为应该更"傻瓜"而不是把那么多细节都扔给开发者.

好了reopen这个新兴方法应该算是彻底搞明白了,原来看似吹嘘的很神的新东西,还是老瓶子装新酒呀,呵呵....

末了,代码再完善下:

if (iSearch != null) {
    // 新修改的reopen方法
    Boolean isNew = false;
    try {
     IndexReader irOld = reOpen(iSearch);
     if (irOld != null) {
      isNew = true;
      // isearchMap.remove(searchBean.getIsearchName());
      isearchMap.put(searchBean.getIsearchName(),
        new IndexSearcher(irOld));
     }
    } catch (Exception e) {
     logger.error(e);
    } finally {
     if (isNew) {
      //保证关闭掉资源,否则连接数一多把机器io挂死
      iSearch.close();
      iSearch = null;
     }
     logger.info(">>>>>>>>>>>>>one search has been reopen:"
       + searchBean.getIsearchName());
    }




lucene 的 reopen的问题

[不指定 2008/09/16 13:32 | by edwardproAdmin ]
按照文档说明reopen可以解决更新索引的问题,但试用下来有几个问题:

1 无法真正reopen,索引往往没有更新,原因不详,需要查lucene的源代码
2 lucene的实例id不会改变,原来以为里边的版本id标识会变,现在似乎不变,这直接导致了,我的缓存标记无法得到更新而读取了老的缓存.

另外在使用memeched时,大家一定要用:

http://bleu.west.spy.net/~dustin/projects/memcached/

另外一个链接池有很大问题,导致大访问量出现链接死锁问题.


另外jdk方面觉得可以直接放弃sun的了,用它的套壳版本: oracle的 jrmc 或者 jrrc都行,性能和设置都比sun的jdk简单.

lucene中的filter其实并不起眼,大家对其对性能的影响也不是很关注,但实际上filter是除了单纯搜索以外,其他搜索附加功能的必选组件,其性能很大程度上会直接影响搜索的性能,之前我一直认为filter的性能比query高,但事实说明并不完全如此(这里所说的负荷是指io消耗并不是cpu),实际上在lucene中充满着各种io流,也就是说很多东西都无法从根本上保存,这也给缓存带来了很大难度(这个问题看似简单,但是在超复杂的组合查询下,缓存可能会几乎无用,原因就是key怎么把握)

首先来看看filter的接口定义:
public abstract class Filter implements java.io.Serializable {
  public abstract BitSet bits(IndexReader reader) throws IOException;
}

简单明了从reader中知道哪些记录是可以读出来的用true false放在bitsets中,然后再用这去和总集合做and操作得到剩余记录数,然后再通过query查询.原理知道了,下面来考虑下它的缓存:
 缓存filter本身,由于他是序列化对象,那么已经具备了缓存的条件,但是这是一个错误,因为你缓存了这个类,而当你把参数reader拿出来依然会和机器产生io,因此这是极其不恰当的方法,应该缓存它的结果.
在lucene中有这么几个和filter有关的类:
CachingWrapperFilter
CachingSpanFilter
RemoteCachingWrapperFilter
FilterManager

其实我想质疑前两个,为什么呢,请看他的源码:
  protected transient Map cache;

他放置缓存的map居然是transient的,这意味着即使你把它实例在static中这个变量依然会每次要new的,这样的缓存有意义吗?我看不出他怎么缓存的
  /**
   * A transient Filter cache.  To cache Filters even when using {@link org.apache.lucene.search.RemoteSearchable} use
   * {@link org.apache.lucene.search.RemoteCachingWrapperFilter} instead.
   */
上面这句注释总算明了了,呵呵.
那么其实RemoteCachingWrapperFilter才是真正的cache类,他的实现借助于filterManager,这个类是我们平时能理解的那种cache结构
  public BitSet bits(IndexReader reader) throws IOException {
    Filter cachedFilter = FilterManager.getInstance().getFilter(filter);
    return cachedFilter.bits(reader);
  }

但这个还不够,第一他的性能我心里没谱,遇到上万的访问怎么办?所以还是要用第三方的缓存,我使用的是memcached,这个东西不介绍了,只有一个问题,就是必须要求对象是可序列化的,这个不难理解,要想网络传输只能治么搞.
我的缓存策略:把最细胞的filter用memached缓存他的结果集,而他的组合fliter用自带的filtermanager管理就好了.filter怎么合在一起上次写过一个,看这里: http://www.edwardpro.com/post/572/

而我这样的道理也是基于filtermanager的key是reader的hashcode,因此他是对应不同的索引的.那么肯定有朋友问怎么刷新呢?太简单了啊,你的key只要有reader或者search的hashcode就可以了,你一旦更新的源hashcode就变化了.(如果你的search和reader的hash不是固定的那么你肯定承受不了100以上的并行访问,io会高得惊人.)

另外一个技巧,是关于rangefilter的,这个东西不错,但是有一点难,在哪里呢?因为他的查询似乎效率不高,因此一定要缓存! 但是key呢?比如我常用的key是timestamp,但是实际中就会发现如果用毫秒的timestamp那么key几乎无用,因为很少相同的,经过改进,我把时间可以用月做单位,查询也是如此,如果你的要求高我觉得做到天就ok了,如果你数据再多用到小时肯定也够了吧,这样filter的缓存会带来极大的性能提升.

那么实际效果呢,在原来使用时候2台集群机(nginx作为前端代理,后部用resin作为应用服务器)io平均1.xx 现在加了缓存之后常年保持在0.2左右!性能得到了几乎5~6倍的提升.而一般查询一个十万当量的+ 5个关键字 + 3个filter 时间大约是<10ms 非命中时大约是 70~80ms 这个速度如果得到同样结果的数据库至少要放大1000倍的时间.

由于我memcached没有做集群是独立的(事实也应该如此,因为你两台机器的reader的hashcode肯定是不一样的,放一起也是这样的结果,这样也没有不好,当一台机器出现问题或者需要更新代码可以用时间差来保证负荷平稳过渡,不像以前一台机器每次重启都是有点怕怕的,只能找空闲时间才敢这么做.

最后要讲的query,其实前面我说了半天没有提到query,query的缓存呢? 其实在lucene中有这么个类:
QueryFilter
这个类简单说就是把query变成filter,那干什么呢?很简单啊,这样任何查询都会变成filter的,所以所有的缓存都是filter!那么从缓存中取出来的filterquery怎么用?

    MatchAllDocsQuery matchAll = new MatchAllDocsQuery();
    result = isearch.search(matchAll, filter, sort);
filter是用我的合成filter组合的,这样消耗就更低了,当然不建议无限制增加系统负荷,因为那样就几乎无法重启了,呵呵.好了基本说到这里,其实最后我想说我的核心思想: 任何query都是filter,lucene就是filter查询,事实是如此的,呵呵.

大家有什么其他方案也可以讨论和交流一下,呵呵.

具体今天看到一个帖子:

http://www.javaeye.com/post/584415?page=1

有个朋友问某个东西spring怎么实现.

我一看这就是思路问题,他的类调用和过程模式完全一样,或者说完全没有套路,但是接下来的讨论就令我很惊讶了,很多人居然能给出解决之道.我的承认我对spring的使用水平还停留在1.x上,2.0的特性基本没用过.但我想说的是这种过度的滥用对自己没有好处,后面还有个朋友说了,为什么spring好啊,因为spring动态管理对象解决了内存泄漏!我的妈,人家是帮助你构建构架的,现在变成了你用来屏蔽水平问题的工具,这到底是可悲还是可喜呢?

又买了原版书

[不指定 2008/06/24 19:06 | by edwardproAdmin ]
这次是restful webservice 这本书评价不错可以一看,问题是中文版50+,英文版32块钱,一咬牙为了20块买了本硬骨头看看...物价涨逼着自己要节约了,呵呵.

不过说实话上次买的高效stl编程(第二版)看得我一场噩梦,这次不知道咋样了,我清楚记得我每天都会抱着书睡着,但是每天只能看几页,唉...不过不管咋样英文不好不好吃饭,还是要耐着性子看,而且这本书文字相对会通俗些,多少有点信心,呵呵.

对guice的盲目评价

[不指定 2008/06/12 23:57 | by edwardproAdmin ]
guice很火热,大家都说好,并且把它和spring相提并论了,带着对google无比的崇敬我开始了我的guice之旅.声明目前还不能算对它有研究我总共就看了2小时的文档外带几个例子,还算不上了解,只是知道而已.但我看完之后总觉得有点不一样的感觉.

如果说guice是一个应用框架我觉得还差一点,实际上不管实现如何,最后总能对应一些东西,很明显它的Module就是spring的applicationcontext.但两者是完全不能对等的guice是一个接口,而application是一个工厂.如果application理解为一个虚工厂的实例的话,那么module就是他的工厂类的接口描述.换句话说guice在入口上是空的,那么有朋友要反驳我了,不是呀,这个module要自己实现的写绑定类的,这样就等于把guice推向了和脚本语言类似的地方,但是别忘记java需要pre-complie的这样这种方法就很不灵活,而spring恰恰用配置解决了这个问题,我个人认为guice的前端依然可以很轻易地套上一个spring或者自己写个简单的配置读取,这样等于把guice拉到了spring的老路上.当然目前我不知道guice有没有什么自动化功能,因为不清楚,如果有,请大家告诉我,那么这点问题就不是太大.

但是guice本身来说它规定了一种模式,但是实现几乎需要你来实现,实际上这是很双刃的,一方面它自由,可以随心所欲,另外一方面它又不能在不进行二次开发情况下达到生产级标准,而且个人的实现会不同,下面写两种我对guice的模式设计:
1 将module工厂化,然后对应每个类的植入都对应一个annoation方法这个方法共同继承了一个来自自己设定的配置虚类,把植入的配置传入就可以直接联系到,而且这样我们会看到如果植入的service对会对应它的一个植入配置类,这个类需要自己维护.
2 学习spring加一个配置就好了,至于别的代码侵入性其实不大,我不认为写@有多大的侵入性这些代码就算移植也能用,当然不能移植到1.4上,呵呵.

当然guice我会继续关注,看看它到底怎么回事,至少现在我还不应该说什么的,因为我根本就不懂,只是想提一些想法和大家共勉.

共享一个filter合并类

[不指定 2008/06/04 18:23 | by edwardproAdmin ]

正好在用随便写了一个,把很多filter合并,不是很完善.

import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Filter;

/**
* @author edward pro
*
*/
public class MultiFilter extends Filter {

/**
*
*/

private List<Filter> filterList = new ArrayList<Filter>();

public List<Filter> getFilterList() {
return filterList;
}

public void setFilterList(List<Filter> filterList) {
this.filterList = filterList;
}

public MultiFilter() {
// TODO Auto-generated constructor stub
}

/*
* (non-Javadoc)
*
* @see org.apache.lucene.search.Filter#bits(org.apache.lucene.index.IndexReader)
*/
@Override
public BitSet bits(IndexReader reader) throws IOException {
// TODO Auto-generated method stub
BitSet bits = new BitSet(reader.maxDoc());
for (Filter filter : this.filterList) {
BitSet tBits = filter.bits(reader);
bits.and(tBits);

}
return bits;
}

public void addFilter(Filter filter) {
this.filterList.add(filter);
}

}

这个想法由来已久,由于全文索引本身的缓存设计,使得其io操作要小于数据库对于频繁读取的操作全文索引绝对有可能在性能上超越数据库(至少能轻松秒杀mysql,mysql那超低的io性能如果不改那肯定是要被sqlserver给灭掉的,只是时间问题)但当你深入这个问题的时候就会有很多问题出现,我最初的需求是利用这个记录搜索关键字进而得到关键字hot排名。问题出现了:

1 IndexerWriter有个特性如果不close始终有部分内容会写不入索引,也就是它的缓冲机制,这是非常致命的,由于在实际应用中,这种写入是多线程的,当你在需要的时候close下以获得更新的时候,那么很可能使得某个写入线程出错,这是非常糟糕的,虽然我们不要求lucene达到acid的水准,但是经常报错是非常有问题的。
2 接着1的问题,同样在reader端需要频繁读入新内容,这可能会造成很大的性能问题,虽然在2.3中已经有了reopen方法,但我尝试过效率绝对没有官方宣称的如此高效,源代码我也看过的确不是那么高效的在我已经优化过的环境里,并没有获得很巨大的优势。

那么现在来看看解决的方法:

首先可以考虑是否使用ram作为临时存储地,不管如何,在ram上close和reopen都要比fs要高速得多,当然这个代价就是无法很大,虽然现在我的生产环境已经是16g内存的机器×2但我依然无法保证能够无限满足需求,有一天满了怎么办?到时候哭都来不及。

第二,考虑在fs上使用多索引,然后merge到主索引的方法,这样我可以利用线程限制极大保证fs上不会在close状态下被写入,close方法上有一个参数 是否等待merge完成,只要在线程模式下,是可以保证得到期望结果的,偶尔io不行的时候也就是偶尔出错一次不会产生太多问题。

好了,上述的方法还没有得到验证,尤其是线程模式下环境是很复杂的,而lucene又要求严格的线程安全,因此大量的实验室少不了的。

其实在我的理想中希望做一套lucene的服务,能够完全兼容于jdbc,这样可以实现服务的从db到全文的0切换,毕竟在like下lucene的效率之高使得数据库系统完全被秒杀。当然进而还有很多问题,比如表和表的关系,在lucene中怎么对应,这是后话,待我想想再来说,先去试验多线程安全模式下的写入。

由lucene集群想到

[不指定 2008/04/11 11:58 | by edwardproAdmin ]
你的搜索可以集群吗?这是一个突然发来的问题。。。其实lucene说实话什么都好就是集群不方便。。。

查看了些资料基本上做集群都是利用系统的集群文件系统等实现的,这个效率不会好。

突然我觉得可以这样的结构来做集群:

一台单机做索引,索引文件存在这台物理机上

然后利用nfs将索引文件映射到多台子机,子机将nfs的内容用RAMDirectory读到内存索引中,然后子机各自使用web服务提供前台访问

前端再使用集群服务器实现前台的集群

当索引更新的时候可使用jms通知子机及时同步nfs和RAMDirectory的内容

这样的集群从性能上应该能有一定的提高。
Tags: ,
趁着清明放假的大好岁月,花了点洗衣服的时间看了点lucene的源码,主要想看看分词那部分。
luncene分词的大概过程是这样的:
1 截断单词
2 过滤干扰信息
3 写入结果

截断,对于英文来书很简单就是用空格和标点符号以及一些特殊用词,这些在系统里已经定义好,当然你也可以适时地改变一下。
过滤,在它的标准算法中会对如下的符号进行过滤:'s 'S  . 这样几种。
写入,这个顾名思义啦

下面谈谈我对中文分词的猜想,之前也用过je分词这样所谓成功作品,但无论性能还是效果都无法达到我的要求,而且它又不公开源码,令人非常失望,所以目前我使用的依然是luncene中的标准分词,也就是单字分词,但单字分词的问题也很明显消耗了极大的存储空间,目前在非压缩状态下,索引是原始文档的1.2~1.3倍之多,这是随着数据积累挺令我担心的问题。所以不得不思考中文的算法过程,我设想的算法应该是这样:
1 截断,利用分词库和常用介词表进行,其中分词库采用首字单词长度逆向排序法匹配,过程:
首先将词库按首字放入hash,然后将同首字的按照长度逆序排列
然后分词的时候先按单字分开,然后依据分词表,匹配 n次 (n是首字列表里的元素个数)并且允许重复匹配,比如中国 中国人 需要重复匹配。
之后同样去除标点符号等干扰因素。
那么我们现在来是想一下它的算法复杂度:
应该是: len(str)×n(str【i】)
试验更高级的算法在多词匹配时利用递归,将后一个字的算法也同时写入,或将减少其算法复杂度。
下周刊有时间的时候尝试写一个分词来看看,是否合理。
Tags: ,

关于group by

[不指定 2008/03/22 10:30 | by edwardproAdmin ]

周五因为在搜索引擎中要使用group by操作,原有的 count发似乎会出现很大的效率问题,所以也简单考虑了下groupby的实现,在sql里我猜想(我对这个算法完全没有概念),首先利用矩阵算法将所有的row排序好,然后以此顺序取出,然后再加入聚合函数count或者sum,也就是说在sql中实际上他是先把按groupby字段索引排序然后再进行数据聚合,最后再拉出需要的数据(这个猜想是基于group by字段需要索引得到的,不知道是否正确)
而在lucene中也是类似的状况,现在我们考虑lucene 大家知道在lucene中得到的结果集是hits,hits实际上和sql中的结果集recordset是一个概念的,即使你得到了一个数百万的结果集,数据也并没有立即从中拉出(这应该就是为什么表需要有主键的原因)。但lucene中有个问题,我无法得到一个多结果集,也就是说我无法得到一个按groupby排序的某个中间结果,我得到的只是输出,因此在group中我只能选择先将所有数据全部读出这条路,那么读出来的数据怎样groupby?我实现的是基于hash表的 数组遍历。是这样的过程:
hash<字段名,groupby结果集>  
groupby结果集<字段值,聚合值>
用这样的hash嵌套实现,算法中我只使用了一次hash循环,但比较大的伤害是,数据集似乎被从头到尾取了一次,会产生很多io,并且由于我的group和查询是一种并行算法,因此等于结果集被取出了两遍(目标中的group是类似taobao的产品列表的类别归类查询页面,所以group是一个独立结果集并非输出结果集),目前测试下来还没有感觉内存消耗有极其大的影响,但不知道在高负荷状态下的水平,因此还需要继续查看。
关于group的算法,如果有更好的办法和算法或者在lucene下有更好的解决方案欢请路过不要吝啬你的idea告诉我吧。

Tags: , ,
一直喜欢看设计模式的书,总是觉得自己搞不清楚所有的设计模式和他们的名字,所以拼命温习着,但效果似乎不大,总还是会说不清楚。但今天自己玩的时候却发现,说到底设计模式不过解耦的过程,把握好类和类的关系和颗粒度,这就是设计模式所要做的,事实上把握这点的时候就会忘记模式,正所谓的无招似有招。就和前几天看一同事的朋友的吉他表演一样,其实是一个道理,人家玩得花只是因为他只考虑音乐本身而不是这个和弦是什么(当然和弦名字还是很清楚的。。。),这就是境界。

对了,推荐大家看本书吧,设计模式的,这本书是国内原创的,和国外那些标准教材不同,看起来更简单,描述语言是.net不过除了反射不一样,java也一样可以看明白语法基本一样的。觉得好主要是里边的例子比老外写的学术性教材更容易接受。
大话设计模式
分页: 1/5 第一页 1 2 3 4 5 下页 最后页 [ 显示模式: 摘要 | 列表 ]