ruby中self,範圍,可見度的用法詳解

來源:互聯網
上載者:User

有些東西在任何時間任何地方表示的意思是不變的,比如整數,你看到的就是它表示的東西。關鍵詞也一樣,你不能使用 def,class 這些關鍵詞作為變數名,所以當你看到它們的時候,你可以很容易知道它們是做什麼的。不過還有很多東西的意思取決於它們所處的情境,也就是它們在不同的時間不同的地方的意思可能是會有變化的。

self 表示的是當前或者預設的對象,在程式啟動並執行時候每次它都會表示一個特定的對象。永遠都只會有一個 self ,但是它表示的東西是會變的。

範圍(scope)的規則決定了變數的可見度。你要知道所在的地方受哪個範圍的影響,這樣你才能明白哪些變數表示的是什麼,不致於把它們跟在其它的範圍的同名變數混淆。

知道當前是在哪個範圍,瞭解 self 表示的對象是誰,這樣你才能明白到底發生了什麼,才可以快速的分析遇到的問題。

下午 5:54 ***

self

下午6:07 ***

使用關鍵詞 self 可以得到當前對象。在程式運行中,有且只有一個 self 。成為 self 有一些特權,一會兒我們會看到。永遠都只會有一個當前對象或者叫 self 。是誰在哪裡會成為 self 是有一些規則的。

在頂級的 self 對象

這裡說的頂級的意思是,在任何的類或模組定義之外的地方。比如,開啟一個空白的檔案,輸入:

x = 1
這樣我們就建立了一個在頂級的本地變數 x ,下面的代碼就是在頂級建立了一個方法:

def m
end
看一下在頂級的 self 是誰,執行一下:

puts self
返回的是:

main
main 是一個特別的詞,它表示的就是 self 本身,比如我們可以這樣試一下:

m = self
在類,模組定義裡的 self

在類與模組定義裡,self 指的就是類或模組對象。

下面這個實驗會告訴你 self 在類的定義與模組的定義裡面表示的是誰,做個實驗:

class C
  puts '類:'
  # self 是 C
  puts self

  module M
    puts '模組:'
    # self 是 C::M
    puts self
  end
  puts '回到類層級:'
  # self 又是 C
  puts self
end
進入類或模組的定義地區以後,類與模組對象就變成了 self。上面的例子,最開始 self 表示的是 C 類這個對象。進入定義模組的地區的時候,self 是 C::M ,表示 C 類裡面嵌套的模組 M。回到定義 C 類以後,self 表示地又會是這個類對象,也就是 C。

在定義執行個體方法裡的 self

在執行個體方法的定義裡的 self 很微秒,因為 Ruby 的解譯器遇到 def/end 以後,它會立即定義方法。這時候方法定義裡的代碼還沒被執行呢。你看到的螢幕上定義的方法,你只知道當方法被調用的時候,self 會是調用方法的那個對象。在定義方法的那個時候,最多你只能說的是,在這個方法裡面的 self 會是未來的那個調用方法的對象。

做個實驗:

class C
  def x
    puts "Class C, method x:"
    puts self
  end
end

c = C.new
c.x
puts "That was a call to x by: #{c}"
會輸出:

Class C, method x:
#<C:0x00000101b381a0>
That was a call to x by: #<C:0x00000101b381a0>
輸出的這個東西:#<C:0x00000101b381a0> ,表示的就是一個 C 的執行個體。在執行 x 方法的時候,self 就是調用它的執行個體對象 c 。

獨立方法與類方法中的 self

執行一個獨立方法的時候,self 表示的就是擁有這個方法的那個對象。

下面這個實驗,先建立了一個對象,又在這個對象裡定義了一個獨立方法,然後調用這個獨立方法,看一下 self 表示的是誰。實驗:

obj = Object.new
def obj.show_me
  puts "Inside singleton method show_me of #{self}"
end

obj.show_me
puts "Back from call to show_me by #{obj}"
會輸出:

Inside singleton method show_me of #<Object:0x007fd189963428>
Back from call to show_me by #<Object:0x007fd189963428>
類方法基本上就是在類對象上定義的獨立方法,再做個實驗:

class C
  def C.x
    puts "Class method of class C"
    puts "self: #{self}"
  end
end
C.x
會輸出:

Class method of class C
self: C
在類的內部我們可以使用 self 表示類的名字:

class C
  def self.x
    puts "Class method of class C"
    puts "self: #{self}"
  end
end
如果我們:

class D < C
end
D.x
輸出的會是:

Class method of class C
self: D
self 作為預設的資訊接收者

2016年9月8日

調用方法就是發送資訊給對象,像這樣:

obj.talk
ticket.venue
'abc'.capitalize
在調用方法的時候如果資訊的接收者是 self,可以忽略掉接收者還有點,Ruby 會使用 self 作為預設的接收者,意思就是你發送的資訊會發送給 self 。像這樣:

talk
venue
capitalize
如果有個變數叫 talk,還有個方法叫 talk,調用 talk 的時候只是用了一個 talk,那麼變數的優先順序會更高一些。遇到這種情況你可以在調用 talk 方法的時候加上 self,像這樣: self.talk,或者加上括弧:talk() 。

class C
  def C.no_dot
    puts '只要 self 是 C,你就可以不用點來調用這個方法'
  end
  no_dot
end

C.no_dot
第一回調用 no_dot 的時候沒有明顯的接收者,Ruby 看到它以後,會判斷你的意思可能是:

self.no_dot
在上面這個例子裡,self.no_dot 跟 C.no_dot 是不一樣的東西,因為我們在定義 C 的區塊裡,這樣 self 就是 C。結果就是方法被調用了,然後我們就看到了輸出了結果。

第二次我們使用 C.no_dot 的時候,已經是在定義類的區塊以外了,所以 C 就不再是 self 了。也就是想要調用 no_dot ,我們就得指定這個信自的接收者,也就是 C。

上面這個例子輸出的結果就是兩次調用 no_dot 方法的結果:

只要 self 是 C,你就可以不用點來調用這個方法
只要 self 是 C,你就可以不用點來調用這個方法
最常見的使用這種無點調用方法,就是當你在另一個執行個體方法裡去調用一個執行個體方法,再看個例子:

class C
  def x
    puts '這是方法 x'
  end

  def y
    puts '這是方法 y,我要無點調用 x'
    x
  end
end

c = C.new
c.y
輸出的結果是:

這是方法 y,我要無點調用 x
這是方法 x
上面調用 c.y 的時候,執行了方法 y ,self 指的是 c(c 是 C 的一個執行個體)。在 y 裡面,使用了 x,它被解釋成資訊要發送給 self ,這樣也就會執行了方法 x 。

有一種情況你不能忽略掉對象加點的形式去調用方法,就是如果方法的名字裡面帶等號(設定器方法),也就是如果你想調用在 self 上的 venue= 這個方法,你需要這樣做:self.venue = 'Town Hall',而不是這樣:venue = 'Town Hall'。因為 Ruby 會一直認為 identifier = value 是在分配值給一個本地變數。

無點方法調用,在一個方法裡使用另一個的方法的時候很有用,再來看一個例子:

class Person
  attr_accessor :first_name, :middle_name, :last_name

  def whole_name
    n = first_name + ' '
    n << "#{middle_name} " if middle_name
    n << last_name
  end
end

david = Person.new

david.first_name = 'David'
david.last_name = 'Black'

puts "David 的全名是:#{david.whole_name}"

david.middle_name = 'Alan'
puts "David 的全名現在是:#{david.whole_name}"
輸出的結果會是:

David 的全名是:David Black
David 的全名現在是:David Alan Black
通過 self 解釋執行個體變數

在 Ruby 程式裡,任何的執行個體變數都會屬於當前對象。

先做個實驗,看看下面的東西會輸出什麼:

class C
  def show_var
    @v = 'I am an instance variable initialized to a string.'
    puts @v
  end
  @v = "instance variables can appear anywhere..."
end

C.new.show_var
會輸出:

I am an instance variable initialized to a string.
在上面的代碼裡,有兩個 @v,一個是在方法定義內,另一個是在方法定義外,它們之間沒有任何聯絡。它們同樣都是執行個體變數,並且名字都是 @v,不過它們不是同一個變數,它們會屬於不同的對象。

第一次出現的 @v 是在方法定義區塊裡,也就是 C 類裡面的一個執行個體方法,這樣 C 類的每一個執行個體對象裡面都會有它們自己的執行個體變數 @v 。

第二次出現的 @v 屬於 C 這個類對象。類本身也是對象。

every instance variable belongs to whatever object is playing the role of self at the moment the code containing the instance variable is executed.
重新再寫一下上面的例子:

class C
  puts "* 類定義區塊"
  puts "  | --- self 是:#{self}"
  @v = '我是 @v'
  puts "  | --- #{@v} 執行個體變數,屬於:#{self} \n\n"

  def show_var
    puts "* 執行個體方法定義區塊"
    puts "  | --- self 是:#{self}"
    puts "  | --- @v 執行個體變數屬於:#{self}"
    print "  | --- @v 的值是:"
    p @v
  end
end

c = C.new
c.show_var
執行代碼輸出的結果是:

* 類定義區塊
 | --- self 是:C
 | --- 我是 @v 執行個體變數,屬於:C

* 執行個體方法定義區塊
 | --- self 是:#<C:0x007fd3c8961f10>
 | --- @v 執行個體變數屬於:#<C:0x007fd3c8961f10>
 | --- @v 的值是:nil
範圍

上午11:15 ***

範圍指的就是標識符的可見度,特別指的是變數與常量。不同類型的標識符有不同的範圍規則。在兩個方法裡使用同一個名字的變數 x ,跟在兩個地方使用全域變數 $x,會有不同的結果。因為本地與全域變數的範圍不一樣。

全域範圍與全域變數

全域範圍覆蓋了整個程式。全域變數用的是全域範圍,在任何地方你都可以使用它們。即使你開啟了新的類或方法的定義,或者 self 的身份變了,初始的全域變數仍然可用。

下面這個例子,在定義類的主體裡可以使用初始化的全域變數:

$gvar = "我是全域變數"
class C
  def examine_global
    puts $gvar
  end
end

c = C.new
c.examine_global
輸出的結果會是:

我是全域變數
本地範圍

class,module,def 都會建立新的本地範圍。

做個實驗:

class C
  a = 1

  def local_a
    a = 2
    puts a
  end

  puts a
end

c = C.new
c.local_a
輸出的結果是:

1
2
第一次出現的本地變數 a ,是在類定義的本地範圍的下面。第二次出現的 a,是在方法定義的本地範圍的下面。結束了方法定義以後,本地範圍回到了類區塊,這裡要求輸出的 a 就是在類區塊這個本地範圍下面聲明的本地變數 a ,它的值是 1 。然後我們建立了一個類的執行個體,調用了它的 local_a 方法,這個方法輸出的是在方法定義範圍下面聲明的本地變數 a 的值,也就是 2 。

在嵌套類與模組的時候,每次遇到新的定義區塊以後就會建立一個新的本地範圍。

再試一下:

class C
  a = 5

  module M
    a = 4

    module N
      a = 3

      class D
        a = 2

        def show_a
          a = 1
          puts a
        end
        puts a
      end
      puts a
    end
    puts a
  end
  puts a
end

d = C::M::N::D.new
d.show_a
輸出的結果會是:

2
3
4
5
1
任何的 class,module 或 method 都會開啟一個新的本地範圍,每個範圍都可以有屬於自己的全新的本地變數。

本地範圍與 self

先看個例子:

class C
  def x(value_for_a, recurse=false)
    a = value_for_a
    print "現在 self 是: "
    p self
    puts "現在 a 是:"
    puts a
    if recurse
      puts "\n調用自己..."
      x("a 的第二個值")
      puts "調用自己結束以後,a 是:"
      puts a
    end
  end
end

c = C.new
c.x("a 的第一個值", true)
啟動並執行結果會是:

現在 self 是: #<C:0x007fa5fa162388>
現在 a 是:
a 的第一個值

調用自己...
現在 self 是: #<C:0x007fa5fa162388>
現在 a 是:
a 的第二個值
調用自己結束以後,a 是:
a 的第一個值
執行個體方法 C#x 有兩個參數,第一個參數是要分配給變數 a 的值,第二個參數是一個標記,表示是否要調用它自己。方法的第一行初始化了一個本地變數 a ,下行的幾行代碼就是輸出了表示  self 還有 a 的值的字串。

然後到了做決定的時候了(if recurse),調用自己不調用自己,這會由 recurse 變數決定。如果調用自己,就會調用方法 x ,調用的時候沒有指定 recurse 參數的值,這個參數預設的值是 false,所以調用它自己的時候就不會再繼續的調用它自己了。

調用自己的時候給 value_for_a 參數設定了一個不同的值(“a 的第二個值”),也就是會在調用的時候輸出不同的資訊。不過調用自己回來以後,我們發現這次啟動並執行 x 裡的 a 的值沒有改變(還是 “a 的第一個值”)。也就是每一次我們調用 x 的時候,都會產生一個新的本地範圍,即使 self 並沒有改變。

常量的範圍

下午1:06 ***

在類與方法定義區塊裡可以定義常量。如果你知道定義時使用的嵌套,你就可以在任何地方訪問到常量。來看個例子:

module M
  class C
    class D
      module N
        X = 1
      end
    end
  end
end
比如我要訪問在模組 N 裡定義的常量 X,這樣做:

M::C::D::N::X
得到常量的位置也可以是相對的,下面的例子驗證了這個說法:

module M
  class C
    class D
      module N
        X = 1
      end
    end
    puts D::N::X
  end
end
在 C 類裡,得到模組 N 裡的 X,用的是 D::N::X 。

有時候你不想使用相對的路徑得到常量。比如我們想建立的類跟 Ruby 內建的類名字一樣,比如 Ruby 裡面有個 String(字串) 類,如果你建立了一個 Violin(小提琴),裡面也可能會有一個 String (弦)。像這樣:

class Violin
  class String
    attr_accessor :pitch
    def initialize(pitch)
      @pitch = pitch
    end
  end

  def initialize
    @e = String.new("E")
    @a = String.new("A")
    ...etc...
上面使用 String 的時候指的是我們自己定義的 String 類,如果你想使用 Ruby 內建的 String 類,可以使用常量分隔字元(::,兩個冒號),像這樣:

::String.new('hello')
類變數,範圍,可見度

下午1:50 ***

類變數是維護類狀態用的,它們的名字用兩個 @ 符號開頭,比如 @@var。類變數並不是類範圍,它們是類層次範圍。類變數提供了在類與類的執行個體對象之間共用資料的機制,也就是類變數在類方法定義與執行個體方法定義上都是可見的,有時候在頂級的類定義上也是。除此以外,類變數在其它的對象上是不可見的。

來看個例子,先用類方法 Car.add_make(make) 註冊洗車生產商,然後用 Car.new(make) 造幾輛汽車:

Car.add_make("Honda")
Car.add_make("Ford")

h = Car.new("Honda")
f = Car.new("Ford")
h2 = Car.new("Honda")
程式會告訴你建立的汽車:

建立新的 Honda!
建立新的 Ford!
建立新的 Honda!
下午2:08 **

下午2:27 **

同一個汽車生產商生產了多少個 h2?我們會使用執行個體方法 make_mates 查到:

puts "統計 h2 汽車的數量..."
puts "一共有 #{h2.make_mates} 輛"
一共有多少輛汽車?這需要用到類,而不是每個單獨的汽車,所以我們可以問一下類:

puts "統計汽車的總數..."
puts "一共有 #{Car.total_count} 輛"
輸出的應該是:

統計汽車的總數...
一共有 3 輛"
試一下建立一輛沒有汽車生產商的汽車:

x = Car.new("Brand X")
會報錯:

car.rb:21:in `initialize': No such make: Brand X. (RuntimeError)
代碼如下:

class Car
  @@makes = []
  @@cars = {}
  @@total_count = 0
  attr_reader :make
  def self.total_count
    @@total_count
  end

  def self.add_make(make)
    unless @@makes.include?(make)
      @@makes << make
      @@cars[make] = 0
    end
  end

  def initialize(make)
    if @@makes.include?(make)
      puts "建立新的汽車生產商:#{make}"
      @make = make
      @@cars[make] += 1
      @@total_count += 1
    else
      raise "沒有汽車生產商:#{make}"
    end
  end

  def make_mates
    @@cars[self.make]
  end
end
在類的頂部定義了三個類變數。@@makes 是一個數組,儲存汽車生產商的名字。@@cars 是 hash,裡面是名值對類型的資料。@@cars 裡的資料的名字是汽車生產商汽車的汽車,對應的資料是汽車的數量。@@total_count 裡面儲存的是一共生產了多少輛汽車。

Car 類裡還有個 make 可讀屬性,建立了汽車以後必須設定 make 屬性的值。這裡沒有關於汽車生產商的可寫的屬性,因為我們不希望類的代碼之後可以改變已有的汽車生產商。

要訪問到 @@total_count 類變數,Car 類裡還定義了一個 total_count 方法,它會返回類變數當前的值。還有一個類方法是 add_make,這個方法接收一個參數,會把參數的值放到表示汽車生產商的數組裡,用的是 << 操作符。在這個方法裡我們先要確定添加的汽車生產商還不存在,如果不存在就把它放到表示汽車生產商的類變數裡 @@makes,同時也會設定一下 @@cars ,讓這個汽車生產商生產的汽車等於零。意思就是還沒有這個汽車生產商生產的汽車。

然後到了 initialize 方法了,在這裡建立新的汽車。每輛新車都需要一個汽車生產商,如果汽車生產商不存在,也就是它不在 @@makes 數組裡,就觸發一個錯誤。如果汽車生產商存在,我們會為汽車的 make 屬性設定合適的值,讓這個汽車生產商生產的汽車的數量增加一(@@cars),同時也讓生產的汽車總數增加一(@@total_count)。

還有一個 make_mates 方法,可以返回某個汽車生產商生產的所有的汽車。

注意上面在 initialize 方法裡,還有在類方法裡,比如 Car.total_count,Car.add_make 上面,都使用了類變數。類內部的執行個體方法 initialize,與類方法是在不同的範圍下。但它們屬於同一個類,所以可以使用類變數在它們之間共用資料。

類變數與類層次

之前我們已經說過了,類變數使用的不是類範圍,也是類層次的範圍。看個例子:

class Parent
  @@value = 100
end

class Child < Parent
  @@value = 200
end

class Parent
  puts @@value
end
輸出的結果會是 200。Child 是 Parent 的一個子類,也就是 Parent 與 Child 共用同樣的類變數。在 Child 裡面設定 @@value 的時候,你設定的是唯一的在 Parent 與 Child 上的 @@value 。

下午3:17 ***

方法訪問規則

下午3:18 ***

現在我們已經知道了 Ruby 程式會發送資訊給對象,對象主要乾的事兒就是對這些資訊做出回應。有時候,對象希望可以給自己發送資訊,但是不希望別人給它們發送資訊。這種情況,我們可以讓方法變成私人的。

訪問有幾個存取層級,私人(private),保護(protected),公開(public)。public 是預設的存取層級,發送給對象的資訊大部分調用的就是有公開存取層級的方法。

私人方法

把對象想成是一個你要求讓他做任務的人,比如你想讓某個人給你烤個蛋糕,為了給你烤這個蛋糕,烤蛋糕的人會做一系列的任務,比如打個雞蛋,和個面什麼的。烤蛋糕的人會做這些事兒,不過他可能並不想對所有這些事情做出回應。你要求的只是“請給個烤個蛋糕”。剩下的事兒交給蛋糕師就可以了。

用代碼類比一下,建立個檔案名稱字是 baker.rb,代碼如下:

class Cake
  def initialize(batter)
    @batter = batter
    @baked = true
  end
end

class Egg
end

class Flour
end

class Baker
  def bake_cake
    @batter = []
    pour_flour
    add_egg
    stir_batter
    return Cake.new(@batter)
  end

  def add_egg
    @batter.push(Egg.new)
  end

  def stir_batter
  end

  private :pour_flour, :add_egg, :stir_batter
end
上面用了一個 private 方法,你可以把想變成私人方法的名字告訴它。如果不加參數,它就像是一個開關,它下面定義的所有的執行個體方法都會是私人方法,直到調用 public 或 protected 。

你不能:

b = Baker.new
b.add_egg
這樣調用 add_egg 會報錯:

`<main>': private method `add_egg' called for #<Baker:0x00000002aeae50> (NoMethodError)
因為 add_egg 是一個私人方法,調用它的時候你指定了某個具體的接收對象,這是不允許的。

如果我們不加資訊的接收者:

add_egg
是否能單獨調用這個方法?資訊會發送到哪裡?如果沒有對象處理資訊,那方法怎麼被調用?調用方法的時候如果不指定資訊的接收者,Ruby 會把資訊發送給當前對象,也就是 self 表示的那個對象。

你可以推斷出來,能對 add_egg 這個資訊作出回應的對象,只能是 self 表示的那個可以對 add_egg 作出回應的對象。也就是我們只能在當 self 是 Baker 的執行個體的時候才能調用 add_egg 這個執行個體方法。

私人方法與獨立方法

私人方法與獨立方法不是一回事。獨立方法只屬於一個對象。私人方法可以屬於多個對象,不過只有在正確的情況下才能被調用。決定可以調用私人方法的不是你發送資訊到的對象,而是你發送資訊的時候 self 表示的那個對象。

保護方法

保護方法是一種溫柔點私人方法。規則像這樣:

you can call a protected method on an object x, as long as the default object (self) is an instance of the same class as x or of an ancestor or descendant class of x’s class.
保護方法主要的目的就是,你可以在某個類的一個執行個體上使用這個類的另一個執行個體去做點事兒。來看個例子:

class C
  def initialize(n)
    @n = n
  end

  def n
    @n
  end

  def compare(c)
    if c.n > n
      puts "另一個對象的 n 更大一些"
    else
      puts "另一個對象的 n 一樣或更小一些"
    end
  end
 
  protected :n
end

c1 = C.new(100)
c2 = C.new(101)
c1.compare(c2)
上面這個例子就是去拿 C 類的一個執行個體跟它的另一個執行個體比較。這個比較依賴調用方法 n 返回的結果。做比較的這個對象(例子是 c1 對象)需要讓另一個對象(c2)去執行它的 n 方法。也就是 n 不能是一個私人方法。

這就需要使用一個保護方法。讓 n 成為保護方法,而不是私人方法,c1 可以讓 c2 去執行方法 n,因為 c1 跟 c2 是同一個類的執行個體對象。但是如果你試著在 C 對象上面調用 n 方法,會失敗,因為 C 並不是 C 類的一個執行個體。

子類也會繼承使用超級類上的方法訪問規則,不過你可以在子類裡覆蓋掉這些規則。

下午4:35 ***

頂級方法

下午4:35 ***

用 Ruby 做的最自然的事情就是去設計類,模組,還有執行個體化類。不過有時候你想快速的寫一些指令碼,不想把代碼放到一個類裡面,你可以在頂級(top-level)去定義與使用這些方法。做這樣的事兒就是在頂級預設的對象裡面寫代碼,這個對象叫 main,它是自動產生的 Object 的一個執行個體,這麼做主要是因為必須得有一個東西是 self,即使是在頂級。

定義頂級方法

在頂級定義個方法:

def talk
  puts 'hello'
end
在頂級上定義的方法會作為 Object 類的一個執行個體上的私人的方法。上面的代碼就相當於是:

class Object
  private

  def talk
    puts 'hello'
  end
end
調用這些方法的時候必須使用裸字風格,也就是不能指定資訊的接收者,因為它們是私人方法。Object 的私人執行個體方法可以在任何地方調用,因為 Object 是在所屬的方法尋找路徑裡面,所以頂級的方法會一直有效。

再看個例子:

def talk
  puts 'hello'
end

puts "不加接收者執行 talk"
talk
puts "加個接收者執行 talk"
obj = Object.new
obj.talk
第一次執行 talk 能成功,第二次執行的時候會報錯,因為調用私人方法的時候不能指定接收者。

預定義的頂級方法

puts,print 都是 Kernel 的內建的私人執行個體方法,查看所有在 Kernel 上提供的私人方法:

ruby -e 'p Kernel.private_instance_methods.sort'

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.