重温Observer模式--热水器·改(转载)

引言

在 C#中的委托和事件 一文的后半部分,我向大家讲述了Observer(观察者)模式,并使用委托和事件实现了这个模式。实际上,不使用委托和事件,一样可以实现Observer模式。在本文中,我将使用GOF的经典方式,再次实现一遍Observer模式,同时将讲述在 C#中的委托和事件 一文中没有提及的推模式(Push)和拉模式(Pull)。

设计思想概述

在 C#中的委托和事件 一文后半部分中我已经较详细的讲述了Observer设计模式的思想,所以这里仅简单的提及一下。Observer设计模式中实际上只包含了两类对象,一个是Subject(主题),一个是Observer(观察者)。它们之间的角色是:

  • Subject:主题(被监视对象),它往往包含着Observer所感兴趣的内容。
  • Observer:观察者,它观察Subject。当Subject中的某件事发生的时候(通常是它所感兴趣的内容改变的时候),会被自动告知,而Observer则会采取相应的行动(通常为更新自身状态或者显示输出)。

它们之间交互的核心工作流程就是:

  1. Subject 提供方法,比如Register()和UnRegister(),用于Observer进行注册(表示它对Suject感兴趣)和取消注册(不再对它感兴趣)。
    • Register()方法实现为:它接收一个Observer的引用作为参数,并保存此引用。
    • 保存的方式通常为在 Subject内声明一个集合类,比如:List<Observer>。
    • 一个Subject可以供多个Observer注册。
  2. 调用Subject实例的Register()方法,并将一个Observer的引用传递进去。
  3. Observer 包含一个Update()方法,此方法供 Subject(通过保存的Observer的引用)以后调用。
  4. Subject 包含一个Notify()方法,当某件事发生时,调用Notify(),通知Subject。
    • Notify的方法实现为:遍历保存Observer引用的集合类,然后在Observer的引用上调用Update方法,更新Observer。
    • 某件事是一个不确定的事,对于热水器来说,这个事就是“温度达到一定高度”。它对外界暴露的方法,应该是“烧水” -- BoilWater(),而不是Notify(),所以Notify通常实现为私有方法。

Observer 向 Subject 注册的序列图表示如下:

Subject事件触发时,通知Observer调用Update()方法的序列图如下:

模式的接口定义

按照面向对象设计的原则:面向接口编程,而非面向实现编程。那么现在应该首先定义Subject和Observer的接口,我们可能很自然地会想到将这两个接口分别命名为 ISubjcet 和 IObserver。而实际上,据我查阅的一些资料,这里约定俗成的命名为:IObservable 和 IObserver,其中由 Subject 实现 IObservable。

NOTE:可能很多人和我当初一样困惑,命名为ISubject不是很好么,为什么叫 IObservable?我参考了一些资料,大概的解释是这样的:接口定义的是一个行为,表示的是一种能力,所以对于接口的命名最好用动词的形容词或者名词变体。这里,Observe是一个动词,意为观察,Observer是动词的名词变体,意为观察者;Observable是动词的形容词变体,表示为可观察的。类似的例子有很多,比如IComparable 和 IComparer 接口、IEnumerable 和 IEnumerator 接口等。

现在我们先来看Subject需要实现的接口IObservable。

IObservable接口

首先创建解决方案ObserverPattern,并在其下添加控制台项目ConsoleApp,然后假如IObservable.cs文件,来完成这个接口。如同我们上面分析的,Suject将实现这个接口,它只用定义两个方法 Register()和Unregister:

public interface IObservable {
    void Register(IObserver obj);       // 注册IObserver
    void Unregister(IObserver obj);     // 取消IObserver的注册
}

注意它的两个方法接收 IObserver类型的对象,分别用于注册和取消注册。

IObserver 接口

现在我们再来完成IObserver接口,所有的Observer都需要实现这个接口,以便在事件发生时能够被 自动告知(自动调用其Update()方法,改变自身状态),它仅包含一个Update()方法:

public interface IObserver {
    void Update();      // 事件触发时由Subject调用,更新自身状态
}

再强调一遍,这里的关键就是Update()方法不是由Observer本身调用,而是由Subject在某事发生时调用。

抽象基类 SubjectBase

注意到上面序列图中的Container(容器),它用于保存IObserver引用的方式,对于很多IObservable的实现来说可能都是一样的,比如说都用List<IObserver>或者是Hashtable等。所以我们最好再定义一个抽象类,让它实现 IObservable 接口,并使用List<IObserver>作为容器的一个默认实现,以后我们再创建实现IObservalbe的类(Subject),只需要继承这个基类就可以了,这样可以更好地代码重用:

public abstract class SubjectBase : IObservable {
    // 使用一个 List<T> 作为 IObserver 引用的容器
    private List<IObserver> container = new List<IObserver>();
   
    public void Register(IObserver obj) {
       container.Add(obj);
    }

    public void Unregister(IObserver obj) {
       container.Remove(obj);
    }

    protected virtual void Notify() {       // 通知所有注册了的Observer
       foreach (IObserver observer in container) {
           observer.Update();           // 调用Observer的Update()方法
       }
    }
}

有了这样两个接口,一个抽象类我们的UML类图便可以画出来了:

注意这里也可以不使用IObservable接口,直接定义一个抽象类,定义IObservable接口能进一步的抽象,更灵活一些,可以基于这个接口定义出不同的抽象类来(主要区别为Container的实现不同,可以用其他的集合类)。

Observer模式的实现

现在我们来实现Observer模式,我们先创建我们的实体类(Concrete Class):热水器(Heater),报警器(Alarm),显示器(Screen)。其中,热水器是Subject,报警器和显示器是Observer。报警器和显示器关心的东西是热水器的水温,当热水器的水温大于97度时,显示器需要显示“水快烧开了”,报警器发出声音,也提示“嘟嘟嘟,水快烧开了”。

下面的代码非常的简单明了,也添加了注释,我就不做说明了:

热水器(Subject)的实现

热水器继承自SujectBase基类,并添加了BoilWater()方法。

public class Heater : SubjectBase {
    private string type;              // 添加型号作为演示
    private string area;              // 添加产地作为演示
    private int temprature;         // 水温

    public Heater(string type, string area) {
       this.type = type;
       this.area = area;
       temprature = 0;
    }

    public string Type { get { return type; } }
    public string Area { get { return Area; } }

    public Heater() : this("RealFire 001", "China Xi'an") { }

    // 供子类覆盖,以便子类拒绝被通知,或添加额外行为
    protected virtual void OnBoiled() {
       base.Notify(); // 调用父类Notify()方法,进而调用所有注册了的Observer的Update()方法
    }

    public void BoilWater() {       // 烧水
       for (int i = 0; i <= 99; i++) {
           temprature = i+1;
           if (temprature > 97) {       // 当水快烧开时(温度>97度),通知Observer
              OnBoiled();
           }
       }
    }
}

报警器 和 显示器 (Observer)的实现

报警器(Alarm)和显示器(Screen)的实现是类似的,仅仅为了说明多个Observer可以注册同一个Subject。

// 显示器
public class Screen : IObserver {

    // Subject在事件发生时调用,通知Observer更新状态(通过Notify()方法)
    public void Update() {
       Console.WriteLine("Screen".PadRight(7) + ": 水快烧开了。");
    }
}
// 报警器
public class Alarm : IObserver {
    public void Update() {
       Console.WriteLine("Alarm".PadRight(7) + ":嘟嘟嘟,水温快烧开了。");
    }
}

运行程序

接下来,我们运行一下程序:

class Program {
    static void Main(string[] args) {

       Heater heater = new Heater();
       Screen screen = new Screen();
       Alarm alarm = new Alarm();

       heater.Register(screen);     // 注册显示器
       heater.Register(alarm);         // 注册热水器

       heater.BoilWater();             // 烧水
       heater.Unregister(alarm);    // 取消报警器的注册

       Console.WriteLine();
       heater.BoilWater();             // 再次烧水
    }
}

输出为:

Screen : 水快烧开了。
Alarm  :嘟嘟嘟,水快烧开了。
Screen : 水快烧开了。
Alarm  :嘟嘟嘟,水快烧开了。
Screen : 水快烧开了。
Alarm  :嘟嘟嘟,水快烧开了。

Screen : 水快烧开了。
Screen : 水快烧开了。
Screen : 水快烧开了。

推模式 和 拉模式

像上面这种实现方式,基本上是没有太大意义的。比如说,我们通常会希望在Screen上能够即时地显示水的温度,而且当水在100度的时候显示“水已经烧开了”,而非“水快烧开了”。我们还可能希望显示热水器的型号和产地。所以我们需要 在Observer的Update()方法中能够获得 Subject中所发生的事件的进展状况 或者事件触发者Suject的状态和属性。在本例中事件的进展状况,就是水的温度;事件触发者(Suject)的状态和属性,则为 热水器的型号和产地。此时,我们有两种策略,一种是 推模式,一种是拉模式,我们先看看推模式。

Observer中的推模式

顾名思义,推模式就是Subject在事件发生后,调用Notify时,将事件的状况(水温),以及自身的属性(状态)封装成一个对象,推给Observer。而如何推呢?当然是通过Notify()方法,让Notify()方法接收这个对象,在Notify()方法内部,再次将对象传递给Update()方法了。那么现在要做两件事:1、创建新类型,这个类型封装了我们想要推给Observer(显示器)的事件进展状况(水温),以及事件触发者Subject(热水器)的属性(或者叫状态)。

我们在ObserverPattern解决方案下重新建一个控制台项目,起名为ConsoleApp2,并设置为启动项目。将上一项目ConsoleApp中的文件复制进来,然后我们创建一个新类型BoiledEventArgs,用它来封装我们推给Observer的数据。

public class BoiledEventArgs {
    private int temperature;     // 温度
    private string type;         // 类型
    private string area;         // 产地

    public BoiledEventArgs(int temperature, string type, string area) {
       this.temperature = temperature;
       this.type = type;
       this.area = area;
    }

    public int Temperature { get { return temperature; } }
    public string Type { get { return type; } }
    public string Area { get { return area; } }
}

注意这个类型的命名虽然为BoiledEventArgs,但是和.Net中的内置类型EventArgs没有任何联系,只是起了这样一个名字。

2、我们需要依次修改 IObserver接口,Screen类的Update()方法,SubjectBase类,以及Heater类,让他们可以接收这个EventArgs参数。出于示范的目的,后面的例子我都将不再使用警报器Alarm类,它的存在仅仅是为了说明多个Observer可以注册一个Subject,上面我们已经示范过了,所以现在我们把它删掉。

我们先来看下IObserver接口:

public interface IObserver {
    // 推模式的实现方式,接收一个BoiledEventArgs
    void Update(BoiledEventArgs e);
}

接口变了,显示器(Screen)的实现也需要修改:

public class Screen : IObserver {
    private bool isDisplayedType = false;      // 标记变量,标示是否已经打印过
    public void Update(BoiledEventArgs e) {

       // 打印产地和型号,只打印一次
       if (!isDisplayedType) {
           Console.WriteLine("{0} - {1}: ", e.Area, e.Type);
           Console.WriteLine();
           isDisplayedType = true;
       }

       if (e.Temperature < 100) {  
           Console.WriteLine(
              String.Format("Alarm".PadRight(7) + ":水快烧开了,当前温度:{0}。", e.Temperature));
       } else {
           Console.WriteLine(
              String.Format("Alarm".PadRight(7) + ":水已经烧开了!!"));                
       }
    }
}

现在可以看到,在Update()方法中,通过传递进来的BoiledEventArgs参数,我们可以获得事件进展(温度),以及事件触发者的信息(产地和型号)了。

接下来我们看这个 BoiledEventArgs是如何传递给 Update()方法的,我们看下SubjectBase基类 和 热水器Heater需要做怎样的修改:

public abstract class SubjectBase : IObservable {
    // 其余略...

    protected virtual void Notify(BoiledEventArgs e) {    // 通知所有注册了的Observer
       foreach (IObserver observer in container) {
           observer.Update(e);          // 调用Observer的Update()方法
       }
    }
}

public class Heater : SubjectBase {
    // 其余略 ...

    // 供子类覆盖,以便子类拒绝被通知,或者添加额外行为
    protected virtual void OnBoiled(BoiledEventArgs e) {
       base.Notify(e);          // 调用基类方法,通知Observer
    }

    public void BoilWater() {       // 烧水
       for (int i = 0; i <= 99; i++) {
           temprature = i + 1;
           if (temperature > 97) {      // 当水快烧开时(温度>97度),通知Observer
             BoiledEventArgs e = new BoiledEventArgs(temperature, type, area);
              OnBoiled(e);
           }
       }
    }
}

我们看到,在事件发生时(水温>97度),我们根据事件进展状况和热水器的属性创建了BoiledEventArgs类型的实例,并且传递给了OnBoiled()方法,进而调用了基类的方法,传递了该实例。

我们再次对程序进行一下测试:

class Program {
    static void Main(string[] args) {
       Heater heater = new Heater();
       Screen screen = new Screen();

       heater.Register(screen);     // 注册显示器
       heater.BoilWater();             // 烧水
    }
}

输出为:
ChinaXi'an - RealFire 001:

Alarm  :水快烧开了,当前温度:98。
Alarm  :水快烧开了,当前温度:99。
Alarm  :水已经烧开了!!

Observer 中的拉模式

继续进行之前,我们在ObserverPattern解决方案下,再创建一个新的Console项目,命名为ConsoleApp3,然后把ConsoleApp2 项目下的文件拷贝过来,把启动项目设置为ConsoleApp3。

拉模式的意思就是说,Subject(热水器)在事件发生时(水温超过97度),并非将自身状态封装成对象通过Notify()方法,进而再通过Observer的引用,调用Update()方法传递给Observer(显示器),而是直接将自身的引用(以基类或者Object的形式)传递过去。Observer在Update()方法中,对传递进来的引用进行一个向下转换(Downcast),转换成具体的Subject类(比如热水器),然后通过这个引用调用Subject实体类(热水器)的公共属性获取状态信息(从中把有用数据拉出来 :-)。

我们需要再次对IObserver接口的Update()方法修改,相应的修改还要修改SubjectBase基类、Heater类 以及 IObserver接口的实现--显示器类(Screen)。

public interface IObserver {
    // 拉模式的Update()方法定义
    void Update(IObservable sender);
}

注意这里接收一个IObservable类型作为Update()方法的参数,而IObservable接口本身只包含Regesiter()和Unregister()两个方法,所以在IObserver的实现中,这里要进行向下转换,转换为响应的实体类对象,才能获得对象的属性。这里也可以接受一个Object类型参数。

我们现在看这个接口的实现,显示器类(Screen):

public class Screen : IObserver {
    private bool isDisplayedType = false;

    public void Update(IObservable obj) {

       // 这里存在一个向下转换(由继承体系中高级别的类向低级别的类转换)。
       Heater heater = (Heater)obj;

       // 打印产地和型号,只打印一次
       if (!isDisplayedType) {
           Console.WriteLine("{0} - {1}: ", heater.Area, heater.Type);
           Console.WriteLine();
           isDisplayedType = true;            
       }

       if (heater.Temperature < 100) {     // 通过热水器引用heater获取温度
           Console.WriteLine(
              String.Format("Alarm".PadRight(7) + ":水快烧开了,当前温度:{0}。", heater.Temperature));
       } else {
           Console.WriteLine(
              String.Format("Alarm".PadRight(7) + ":水已经烧开了!!"));                
       }
    }
}

接下来我们再看下 SubjectBase基类,以及热水器Heater的修改:

public class SubjectBase
    // 其余略...

    // 接受一个 IObservable 类型
    protected virtual void Notify(IObservable obj) {      // 通知所有注册了的Observer
       foreach (IObserver observer in container) {
           observer.Update(obj);        // 调用Observer的Update()方法
       }
    }
}

public class Heater : SubjectBase {
    // 其余略...

    // 新添属性 Temperature
    public int Temperature { get { return temperature; } }

    // 供子类覆盖,以便子类拒绝被通知,或者添加额外行为
    protected virtual void OnBoiled() {
       base.Notify(this);           // <-- 将本身传递过去
    }

    public void BoilWater() {       // 烧水
       for (int i = 0; i <= 99; i++) {
           temperature = i+1;
           if (temperature > 97) {         // 当水快烧开时(温度>97度),通知Observer
              OnBoiled();           // <-- 修改了这里
           }
       }
    }
}

注意,Heater类以前不提供对temperature字段的访问,而为了能在Observer(显示器)的Update()方法中的通过引用访问到temperature,我们需要为Heater类再添加一个 Temperature属性:

public int Temperature { get { return temperature; } }

而在调用Notify()方法时,我们通过this关键字将对热水器Heater本身的引用传递了进去:

base.Notify(this);           // <-- 将本身传递过去

我们再来做个测试:

class Program {
    static void Main(string[] args) {
       Heater heater = new Heater();
       Screen screen = new Screen();

       heater.Register(screen);     // 注册显示器
       heater.BoilWater();             // 烧水
    }
}

输出为:
ChinaXi'an - RealFire 001:

Alarm  :水快烧开了,当前温度:98。
Alarm  :水快烧开了,当前温度:99。
Alarm  :水已经烧开了!!

可以看到和前面完全一样的输出。

推模式和拉模式 的区别

那么大家一定想问,使用推模式和拉模式,有什么区别呢?

  • 推模式的好处是 按需供给,想要提供给 Observer端什么数据,就将这些数据封装成对象,传递给Observer,缺点是需要创建自定义的EventArgs对象。
  • 拉模式的好处 则是不需要另外定义对象,直接将自身的引用传递进去就可以了。但是缺点是我们可能会需要暴露我们不想暴露的内部成员,比如本例中的temperature。我们期望将它作为类的内部数据,仅提供给显示器。但是使用拉模式,你只得为它再提供一个公共的Temperature访问器,这样在程序的其他的地方也可以访问到了,比如说在Program里。除此以外,我们不期望Screen可以进行烧水BoilWater()这一动作,但是由于它获得了Heater的引用,而BoilWater()方法又是Public公共的,所以在Update()方法中也具备了对热水器操作的能力,比如调用 BoilWater() 方法。

.Net 中没有内置的IObserver和IObservable接口,因为在.Net中,可以通过委托和事件来完成,但是一样面临选择推模式还是拉模式的问题,何时使用哪种策略完全依赖于设计者,你也可以将两种方式都实现了,比如,将IObserver接口定义成这样:

// 类似微软的实现:两个都用 ...
void Update(Object sender, BoiledEventArgs e);

注意,这里我用得是BoiledEventArgs作为Update()的参数,这里显然不够合适,如果期望这个接口可以为各种Observer服务,而不仅限于烧水这一事件,那么最好定义一个基类 EventArgs,然后对于各种不同的事件,定义不同的EventArgs类,再让它们去继承EventArgs。如此,可以得到下面的接口定义:

void Update(Object sender, EventArgs e);

呵呵,看到这里诸君应该都明白了吧,微软对这个方法原型定义了一个委托,叫做EventHandler:

public delegate void EventHandler(object sender, EventArgs e);

再谈下去又绕到委托和事件了,我们回到主题,将本文的内容做个总结吧。

总结

本文我再次使用热水器的例子实现了Observer设计模式,但这一次我没有使用委托和事件,而是通过经典的GOF方式。我同时还讨论了实现Observer模式时Subject向Observer提供数据值可以采用的两种方式--推模式和拉模式。最后,我们对这两种模式进行了一个简单的比较,并简要介绍了.Net Framework中采用的方式。

感谢阅读,希望这篇文章能给你带来帮助!

转载于:https://www.cnblogs.com/GeneralXU/archive/2009/09/09/1563117.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/262351.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python删除重复值所在的行数_python – 在last中删除具有重复值的行

我有一个制表符分隔文件,看起来像这样chr1 12226559 12227059 TNFRSF1Bchr1 17051560 17052060chr1 17053279 17053779chr1 17338423 17338923 ATP13A2ATP13A2ATP13A2chr1 19577574 19578074 EMC1MRTO4chr1 19578046 19578546 EMC1MRTO4chr1 19638239 19638739 AKR7A2PQLC2PQLC…

hasLayout属性

在IE中&#xff0c;当一个元素的hasLayout属性被触发后&#xff08;不可逆转&#xff09;&#xff0c;就会拥有一个布局&#xff0c;可以进行更多的样式更改操作。 包含该属性的元素有 body html/table tr th td/img/hr/input button file select fieldset textarea/frameset i…

龙图 VP 李翀:数据化运营及云计算下的运维

文 | 龙图 VP 李翀原文地址&#xff1a;当游戏遇上大数据和云计算——谈数据化运营及云计算下的运维龙图做了8年的游戏&#xff0c;真正快速发展起来是在移动互联网这个时代。随着刀塔快速的扩张&#xff0c;我们在各个领域都遇到了非常多的之前没有想到过的状态。发现云计算其…

python多个main方法_Python,main方法未运行(同一文件中有多个类)

上下文&#xff1a;我开发了一个在远程linux服务器上运行的python脚本。使用python3.6.1运行。这个脚本可以工作&#xff0c;但是非常混乱&#xff0c;并且按照程序编写&#xff0c;与OO相反。所以&#xff0c;我把这个脚本重写成两个不同的类。一个主类和一个对象的blueprint类…

鼠标移动时,光标相对于对象的位置

鼠标在一个物体上移动时&#xff0c;能够计算出光标相对于任何其它对象的相对位置。 Code<Grid x:Name"LayoutRoot" Background"White"> <Grid HorizontalAlignment"Left" VerticalAlignment"Top" Name"grid1&q…

怎么判断一个字符串的最长回文子串是否在头尾_【Leetcode每日打卡】最长回文串...

干货预警&#xff1a;所有文章都会首发于我的公众号【甜姨的奇妙冒险】&#xff0c;欢迎watch。一、来历&#xff1a;力扣从3月开始开启了每日一题打卡活动&#xff0c;于是跟风加入了打卡大军&#xff0c;这两天写评论、发题解&#xff0c;没想到反响还不错&#xff0c;收到了…

mysql的存储过程正负数的变化_《转》mysql存储过程语法及范例

《转》mysql存储过程语法及实例 存储过程如同一门程序设计语言&#xff0c;同样包含了数据类型、流程控制、输入和输出和它自己的函数库。 --------------------基本语法-------------------- 一.创建存储过程 create procedure sp_name() begin ......... end 二.调用存《转》…

.NET平台下WEB应用程序的部署(安装数据库和自动配置)

.NET平台下WEB应用程序的部署(安装数据库和自动配置)李洪根在.NET平台下&#xff0c;部署 Web 解决方案是比较方便的。我们可以利用Visual Studio.NET 2003添加一个WEB安装项目&#xff0c;在部署的“文件系统编辑器”中添加项目的主输出和内容文件&#xff0c;非常简易地完成安…

将pcre、zlib等包编译进nginx里去(转)

转摘自&#xff1a;http://www.sudone.com/nginx/nginx_new_install.html一般编译nginx时&#xff0c;都要先安装pcre、zlib等外部支持程序&#xff0c;然后编译安装nginx时指定这些外部支持程序的位置&#xff0c;这样nginx在每次启动的时候&#xff0c;就会去动态加载这些东西…

苹果原壁纸高清_全面屏壁纸高清 | 电影经典台词截图高清

iPhonex手机壁纸高清(苹果xs壁纸&#xff0c;苹果xr壁纸&#xff0c;iPhone11壁纸&#xff0c;安卓全屏壁纸)火影壁纸高清&#xff0c;经典电影台词截图拼接壁纸&#xff0c;电影《去他妈的世界》壁纸&#xff0c;欧美电影截图加上经典台词&#xff0c;2020好看的手机壁纸&…

python字符串切片用法_详解Python字符串切片

在python中,我们定义好一个字符串,如下所示。 在python中定义个字符串然后把它赋值给一个变量。 我们可以通过下标访问单个的字符,跟所有的语言一样,下标从0开始(==,我自己都觉得写的好脑残了) 这个时候呢,我们可以通过切片的方式来截取出我们定义的字符串的一部分。 使用…

Python中yield

在介绍yield前有必要先说明下Python中的迭代器(iterator)和生成器(Generator)。一、迭代器(iterator)在Python中&#xff0c;for循环可以用于Python中的任何类型&#xff0c;包括列表、元祖等等&#xff0c;实际上&#xff0c;for循环可用于任何“可迭代对象”&#xff0c;这其…

关于Services.exe开机CPU内存使用暴增解决方案

这两天系统&#xff08;Windows Server 2003 SP2&#xff09;开机&#xff0c;发现Services.exe进程CPU使用率暴增并且伴随内存狂耗&#xff0c;内存和虚拟内存可以在10分钟之内耗尽。我3G内存呀&#xff0c;外加2G虚拟内存。但诡异的是待内存耗尽后&#xff0c;Services.exe有…

计算机科学中最重要的32个算法

奥地利符号计算研究所&#xff08;Research Institute for Symbolic Computation&#xff0c;简称RISC&#xff09;做了一个调查&#xff0c;投票选出32个最重要的算法&#xff1a; A* 搜索算法——图形搜索算法&#xff0c;从给定起点到给定终点计算出路径。其中使用了一种启发…

mysql 卸载批处理_MYSQL 注册启动 及 停用卸载 批处理脚本 (补)

早前调试mysql接口时候编写的批处理文件。注册启动 my_install.batecho offset MYSQL_DIR%~dp0rem set MYSQL_DIR%cd%if not "%MYSQL_HOME%""" (if not "%MYSQL_HOME%""%MYSQL_DIR%" (echo MYSQL_HOME%MYSQL_HOME% ! MYSQL_DIR%MYSQL…

azkaban config: nodes:_关于Nordic SDK的sdk.config.h

使用MDK对Nordic SDK开发&#xff0c;离不开sdk.config.h。请看以下两个视图&#xff1a;Text Editor视图Configuration Wiard视图咋一看挺神奇的&#xff0c;配置视图话&#xff0c;方便。具体实现和规则就不一一介绍了&#xff0c;网页搜“MDK中Configuration Wiard的使用”。…

SilverLight开发系列第1步:搭建开发环境

来自 http://www.cnblogs.com/kaima/archive/2008/08/17/1269637.html 在缺乏SilverLight中文教程的今天&#xff0c;新人要入门不容易&#xff0c;所以我根据自己阅读英文原档和实践经验&#xff0c;总结一个系列。首先介绍的是开发环境的搭建。 个人强烈推荐安装英文版的开发…

php使用smtp.sina.com邮箱发送邮件

2019独角兽企业重金招聘Python工程师标准>>> 需要写一个smtp发送类 <?php set_time_limit(120);class smtp {/* Public Variables */var $smtp_port;var $time_out;var $host_name;var $log_file;var $relay_host;var $debug;var $auth;var $user;var $pass;var…

计算机图形学方向投稿国外期刊

图形学领域的杂志 &#xff08;主要发表三维重构与可视化的一般理论方面的论文&#xff09; 对图形学领域的杂志&#xff0c;国外一个专家有一个很好的评价&#xff0c;我基本上按照其的意思翻译&#xff0c;加上自己的一点理解&#xff1a; &#xff08;1&#xff09;很牛、具…

java 不存在就创建_Java 判断多级路径是否存在,不存在就创建

Java 判断多级路径是否存在&#xff0c;不存在就创建方案一&#xff1a;(带文件名的Path&#xff1a;如&#xff1a;D:\news\2014\12\abc.text)public boolean isexitsPath(String path)throws InterruptedException{String [] pathspath.split("\\\\");StringBuffer…