`
小鸡啄米
  • 浏览: 38845 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

设计模式 观察者模式 -- ruby/tk小时钟

    博客分类:
  • Ruby
阅读更多

     以前看用java实现设计模式,很是头疼,那么多的java概念融合在里面,完全影响了对模式自身的理解。实现起来也是相当麻烦。但是在ruby中,模式理解起来是那么的容易,倒是觉得使用ruby来理解设计模式挺靠谱的。

     先介绍一种模式-观者者模式。

  百度百科名片 写道

观察者<Observer>模式(有时又被称为发布-订阅<Publish/Subscribe>模式、模型-视图<Model/View>模式、源-收听者<Source/Listener>模式或从属者<Dependents>模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。

 

      通俗点说就是A对象(被观察)通知另一个(一些)对象(观察者)自己发生改变了,改变了什么,至于你们这些对象要做什么就不关我的事了,你们自己做去吧!耦合度就此降低了。。。

 一段简单的Ruby 观察者模式的实现:

 

 

 

#!/usr/bin/env ruby

require 'observer'

class CheckWaterTemperature
  include Observable
  def run
    loop do
      changed()
      notify_observers(Time.now)
      sleep 1
    end
  end
end

class ClockView
  def update (time)
    puts time
  end
end

checker = CheckWaterTemperature.new
checker.add_observer(ClockView.new)
checker.run

 这段中代码包含两个类,顾名思义ClockView是用来展示的,而Clock就是被观察的对象了。当被观察的对象有改变的时候,应该通知观察者们它已经改变了。

在Ruby中通过mix-in方式(类似于java的接口)实现被观察的对象,它的接口有这些:

 

 

#add_observer //增加观察者
#changed        //改变当前的状态
#changed?      // 查询放钱的状态
#count_observers //计数
#delete_observer   //删除一个
#delete_observers //删除一些
#notify_observers //通知观察者们,我已经改变了
 

 而观察者只要实现一个方法update,这样通知发生的时候,观察者要做什么就由update实现来决定了。。很容易理解啊。。

上面的示例是通过控制台实现的,增加一个GUI实现,TK实现的,文档可真不好找啊.. 

 

 

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require 'observer'
require 'thread'
require 'tk'
=begin
     ruby/tk简单的时钟
=end

class Clock
  #观察者模式
  include Observable
  def getPointAngle(time)
    #获取以y轴为线顺时针的角度,例如:3点钟则时针的角度为90度
    sec_angle = time.sec / 60.0 * 360 
    min_angle = time.min / 60.0 * 360 + sec_angle / 360 / 60
    hour_angle = time.hour.divmod(12)[1] / 12.0 * 360 + min_angle / 360 * 30
    #转换成以xy轴的角度,例如3点钟,则时针的角度为0度,12点时针的角度为180度
    return [hour_angle, min_angle, sec_angle].collect do |x|
      x <= 90 ? 90 -x : 450 - x
    end
  end

  def run()
    #一秒种刷新一次界面
    loop do
      angles = self.getPointAngle(Time.now)
      changed()      
      notify_observers(angles)
      sleep 1
    end
  end
end

class ClockView
  LENGTH_ARRAY = [40, 50, 70]
  def initialize(root)
    @cur_sec_line = nil
    @cur_hour_line = nil
    @cur_min_line = nil
    @canvas = TkCanvas.new(root)
    @canvas.pack('side' => 'left', 'fill' => 'both')
    teitem = TkcText.new(@canvas, "text" => "12", "coords" => [95,10]) 
    teitem = TkcText.new(@canvas, "text" => "3", "coords" => [195,95]) 
    teitem = TkcText.new(@canvas, "text" => "6", "coords" => [95,195]) 
    teitem = TkcText.new(@canvas, "text" => "9", "coords" => [5,95]) 
  end
  def update(angles)    
    coords = Array.new
    #将角度转换成在界面上的坐标
    angles.to_a().each_with_index do |mangle, index|
      cy = Math.sin(mangle / 180 * Math::PI) * LENGTH_ARRAY[index]
      cx = Math.cos(mangle / 180  * Math::PI) * LENGTH_ARRAY[index]
      cx = cx + 100
      cy = 100 - cy
      coords[index] = [cx, cy]
    end
    @cur_sec_line != nil and @cur_sec_line.delete()
    @cur_min_line != nil and @cur_min_line.delete()
    @cur_hour_line != nil and @cur_hour_line.delete()

    hline = TkcLine.new(@canvas, 100, 100, coords[0][0], coords[0][1], "width" => "3")
    mline = TkcLine.new(@canvas, 100, 100, coords[1][0], coords[1][1], "width" => "2")
    sline = TkcLine.new(@canvas, 100, 100, coords[2][0], coords[2][1], "width" => "1")

    [hline, mline, sline].map { |aline| 
      aline.fill 'black' 
    }

    @cur_sec_line = sline
    @cur_hour_line = hline
    @cur_min_line = mline
  end
end

root = TkRoot.new do
  title '怀旧时钟'   
  geometry "200x200+1000+80"
end
clock = Clock.new()
clock_view = ClockView.new(root)
clock.add_observer(clock_view)
Thread.new {
  clock.run
}
Tk.mainloop 
分享到:
评论
3 楼 uaymt 2013-05-06  
增加刻度盘与时间的数值显示:
require 'observer'
require 'tk'
require 'thread'

class Clock
  include Observable

  def run
    loop do
      changed()
      notify_observers(Time.now)
      sleep(0.1)
    end
  end
end

class ClockView
  LENGTHS = {:hour => 55.0, :min => 70.0, :sec => 85.0}
  WIDTHS = {:hour => 3.0, :min => 2.0, :sec => 1.0}
  COLORS = {:hour => 'red', :min => 'blue', :sec => 'black'}

  def initialize
    @foot, @lines = nil, {:hour => nil, :min => nil, :sec => nil}
    root = TkRoot.new { title 'Clock'; geometry '200x220+1000+80' }
    @canvas = TkCanvas.new(root)
    @canvas.pack('side' => 'left', 'fill' => 'both')
    TkcOval.new(@canvas, 6, 6, 194, 194).fill('green').outline('magenta')
    TkcOval.new(@canvas, 10, 10, 190, 190).fill('yellow').outline('magenta')
    (0...360).step(6) do |angle|
      from, to, width, color = angle % 30 == 0 ? [87, 97, 3, 'purple'] : [90, 94, 1, 'orange']
      cx, cy, dx, dy = coordinates(angle, from, to)
      TkcLine.new(@canvas, cx, cy, dx, dy, 'width' => width).fill(color)
    end
    TkcText.new(@canvas, 'text' => '12', 'coords' => [100, 20])
    TkcText.new(@canvas, 'text' => '3', 'coords' => [180, 100])
    TkcText.new(@canvas, 'text' => '6', 'coords' => [100, 180])
    TkcText.new(@canvas, 'text' => '9', 'coords' => [20, 100])
  end

  def radian angle
    angle / 180.0 * Math::PI
  end

  def coordinate angle
    ra = radian(angle)
    [Math.cos(ra), Math.sin(ra)]
  end

  def coordinates angle, from, to
    ax, ay = coordinate(angle)
    [100.0 + ax * from, 100.0 - ay * from, 100.0 + ax * to, 100.0 - ay * to]
  end

  def getPointAngle time
    sec_angle = (time.sec + time.nsec / 1_000_000_000.0) * 6.0
    min_angle = (time.min + sec_angle / 360.0) * 6.0
    hour_angle = (time.hour + min_angle / 360.0) * 30.0
    {:hour => 90.0 - hour_angle, :min => 90.0 - min_angle, :sec => 90.0 - sec_angle}
  end

  def update time
    angles = getPointAngle(time)
    angles.each_pair do |index, angle|
      cx, cy, dx, dy = coordinates(angle, 0, LENGTHS[index])
      @lines[index] and @lines[index].delete()
      @lines[index] = TkcLine.new(@canvas, cx, cy, dx, dy, 'width' => WIDTHS[index]).fill(COLORS[index])
    end
    @foot and @foot.delete()
    @foot = TkcText.new(@canvas, 'text' => time.strftime('%a %F %T%:z'), 'coords' => [100, 210])
  end
end

clock = Clock.new()
clock.add_observer(ClockView.new())
Thread.new { clock.run() }
Tk.mainloop()
2 楼 uaymt 2013-05-06  
上文中的 getPointAngle 函数有错误, “90.0 -” 应放到各角度计算完成之后, min_angle / 60.0 应为 min_angle / 12.0; 另外, 角度除以 360 之后得到上级时间刻度的百分比, 再乘以上级单位时间对应的度数, 容易理解并且不容易出错, 修订如下:
  def getPointAngle(time)
    sec_angle = (time.sec + time.nsec / 1_000_000_000.0) * 6.0
    min_angle = (time.min + sec_angle / 360.0) * 6.0
    hour_angle = (time.hour + min_angle / 360.0) * 30.0
    return {:hour => 90.0 - hour_angle, :min => 90.0 - min_angle, :sec => 90.0 - sec_angle}
  end
1 楼 uaymt 2013-04-28  
这确实是个很好的观察者设计模式的范例!
对程序优化及简化如下:
只通知原始事件(时刻), 处理由观察者进行;
秒针改为接近于连续前进(0.1秒更新一次);
时针、分针、秒针改为不同的颜色;
简化了时、分、秒(精准度提高到纳秒)的角度的计算;
用哈希表使代码更加简捷明了.
require 'observer'
require 'tk'
require 'thread'

class Clock
  include Observable

  def run()
    loop do
      changed()
      notify_observers(Time.now)
      sleep 0.1
    end
  end
end

class ClockView
  LENGTHS = {:hour=>40.0, :min=>50.0, :sec=>70.0}
  WIDTHS = {:hour=>3.0, :min=>2.0, :sec=>1.0}
  COLORS = {:hour=>'red', :min=>'blue', :sec=>'black'}
  def initialize(root)
    @lines = {:hour=>nil, :min=>nil, :sec=>nil}
    @canvas = TkCanvas.new(root)
    @canvas.pack('side' => 'left', 'fill' => 'both')
    TkcText.new(@canvas, 'text' => '12', 'coords' => [95,10])
    TkcText.new(@canvas, 'text' => '3', 'coords' => [195,95])
    TkcText.new(@canvas, 'text' => '6', 'coords' => [95,195])
    TkcText.new(@canvas, 'text' => '9', 'coords' => [5,95])
  end

  def getPointAngle(time)
    sec_angle = 90.0 - (time.sec + time.nsec / 1_000_000_000.0) * 6.0
    min_angle = 90.0 - (time.min * 6.0 + sec_angle / 60.0)
    hour_angle = 90.0 - (time.hour * 30.0 + min_angle / 60.0)
    return {:hour=>hour_angle, :min=>min_angle, :sec=>sec_angle}
  end

  def update(time)
    angles = getPointAngle(time)
    angles.each_pair do |index, angle|
      cx = 100.0 + Math.cos(angle / 180.0 * Math::PI) * LENGTHS[index]
      cy = 100.0 - Math.sin(angle / 180.0 * Math::PI) * LENGTHS[index]
      @lines[index] and @lines[index].delete()
      @lines[index] = TkcLine.new(@canvas, 100, 100, cx, cy, 'width' => WIDTHS[index]).fill(COLORS[index])
    end
  end
end

root = TkRoot.new do
  title 'Clock'
  geometry '200x200+1000+80'
end
clock = Clock.new()
clock_view = ClockView.new(root)
clock.add_observer(clock_view)
Thread.new { clock.run }
Tk.mainloop

相关推荐

    src-oepkgs/ruby-ruby2ruby

    src-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2...

    Ruby/tk学习资料

    ### Ruby/tk 学习资料知识点详述 #### 核心概念:Ruby/tk简介与基本操作 **标题**:“Ruby/tk学习资料” **描述**:“自己整理的Ruby/tk学习资料!” **标签**:“Ruby” **部分内容**:RUBY/tk学习文档 在本...

    Marantz马兰士SA-KI Ruby产品说明书.pdf

    Marantz马兰士SA-KI Ruby产品说明书 本文将从Marantz马兰士SA-KI Ruby产品说明书中提取相关知识点...Marantz马兰士SA-KI Ruby产品说明书展示了Marantz品牌的设计理念和技术特点,为音乐爱好者提供了高品质的音频体验。

    ruby186-26

    Ruby 1.8.6 环境配置是学习和使用 Ruby 语言的基础步骤,尤其对于初学者来说至关重要。在本文中,我们将详细介绍如何在 Windows XP 操作系统上配置 Ruby 1.8.6 开发环境,包括安装 Ruby、Rails、SQLite3 驱动以及...

    cocoapods ruby error

    /usr/local/rvm/rubies/ruby-2.1.0/lib/ruby/2.1.0/pathname.rb:422:in `open': No such file or directory @ dir_initialize – /Users/David/.cocoapods/repos (Errno::ENOENT) from /usr/local/rvm/rubies/ruby-...

    freedesktop.org.xml内容错误解决方案文件

    Fetching mimemagic 0.3.10 Installing mimemagic 0.3.10 with native extensions Gem::Ext::BuildError: ERROR: Failed ...RUBYLIBDIR\=C:/Ruby26/lib/ruby/gems/2.6.0/extensions/x86-mingw32/2.6.0/mimemagic-0.3.10

    rh-ruby23-ruby-tcltk-2.3.0-60.el7.x86_64.rpm

    官方离线安装包,测试可用。使用rpm -ivh [rpm完整包名] 进行安装

    ruby-git, ruby/Git是一个 ruby 库,可以通过将系统调用包装到Git二进制文件来创建读取和操作Git存储库.zip

    ruby-git, ruby/Git是一个 ruby 库,可以通过将系统调用包装到Git二进制文件来创建读取和操作Git存储库 用于 ruby的 Git库在 ruby 中使用Git的库。主页项目源代码的Git public 位于:...

    tencentcloud-sdk-ruby

    获取安装在第一次使用云API之前,用户首先需要在腾讯云控制台上申请安全凭证,安全凭证包括SecretId和SecretKey,SecretId是用于标识API调用者的身份, SecretKey是用于加密的加密字符串和服务器端验证签名的密钥。...

    Linux操作系统下轻松配置Ruby的程序环境

    - **安装包**:ruby-1.8.6-p111.tar.gz 接下来按照以下步骤进行安装: 1. 解压安装包: ```bash tar -xvzf ruby-1.8.6-p111.tar.gz ``` 2. 进入解压后的目录: ```bash cd ruby-1.8.6-p111 ``` 3. 配置安装...

    hw-ruby-intro.zip_SaaS软件工程_hw-ruby-intro_ruby _ruby 中文_saas

    4. **高级软件工程**:在学习和实践中,这份“hw-ruby-intro”作业可能涵盖了软件工程的高级主题,如设计模式、重构、测试驱动开发(TDD)、行为驱动开发(BDD)以及代码质量保证。这些技能对于构建复杂、可维护的...

    Ruby_learning_教程-中文版

    Ruby是一种跨平台、面向对象的解释型编程语言。它由松本行弘(Yukihiro ...对于任何想要深入Web开发的学习者来说,Ruby on Rails框架是一个值得学习的工具,而Ruby语言本身则为学习者打开了通往编程世界的大门。

    docker-alpine-ruby:基于Alpine Linux的用于ruby的极简Docker镜像

    高山Ruby 基于Alpine linux的Ruby的极简Docker镜像。 如何使用这张图片 运行交互式Ruby Shell $ docker run -it coopermaa/alpine-ruby 这将为您提供一个交互式Ruby Shell。 运行一行Ruby脚本 $ docker run --rm ...

    Ruby-GNOME2.zip

    Ruby-GNOME2提供GNOME 2.0开发环境的Ruby绑定,目前在活跃更新中。如果你喜欢GTK的界面,你一定会喜欢Ruby-GNOME2。GTK目前提供Linux、 Windows等多个平台的运行库,也就是说使用GTK开发的应用程序可以在多个平台下...

    docker-build-ruby:使用 build-ruby 构建Ruby的 Dockerfile

    Dockerfile brodock/build-ruby 此 Docker 映像将包含 ruby​​-build 以及构建每个 ruby​​ 二进制文件所需的一切,... docker run -i -t brodock/build-ruby /opt/ruby-build/bin/ruby-build -k 2.1.3 /opt/rubies

    docker-dind-ruby:Docker 中的 Docker,预装了 Ruby

    ruby-install预安装在映像中,因此安装新的 Ruby 版本很容易: RUN ruby-install ruby 2.2.1 请注意,默认情况下PATH指向默认的 Ruby 版本。 您可以安装 ruby​​ 版本管理器(如 ),或更改路径: ENV PATH /...

    ruby安装升级及命令自行编译安装非APTGET方式安装升级的办法

    - 配置编译选项:在编译前需确保`/usr/local/ruby-1.9.1`文件夹有适当的权限。执行命令`../configure --prefix=/usr/local/ruby-1.9.1` **步骤三:编译与安装** - 编译源码:执行`make` - 安装编译后的文件:`make...

    Rails-doubletap-RCE:使用路径遍历(CVE-2019-5418)和Ruby对象反序列化(CVE-2019-5420)的Rails 5.2.2上的RCE

    使用路径遍历(CVE-2019-5418)和Ruby对象反序列化的RCE on Rails 5.2.2(CVE-2019-5420) 技术分析: CVE-2019-5418- CVE-2019-5420- //hackerone.com/reports/473888 安全部门: CVE-2019-5418- //groups....

    rh-ruby23-ruby-tcltk-2.3.8-70.el7.x86_64.rpm

    官方离线安装包,测试可用。使用rpm -ivh [rpm完整包名] 进行安装

    homebrew-portable-ruby::automobile:可以从文件系统上的任何位置安装和运行的Ruby版本

    自制的便携式Ruby 可以构建Ruby版本的公式和工具,可以在文件系统上的任何位置安装和运行这些版本。 如何安装这些公式 只需brew install homebrew/portable-ruby/&lt;formula&gt; 。 如何为这些公式构建软件包 苹果系统 在...

Global site tag (gtag.js) - Google Analytics